首页 > 技术文章 > Java多线中基础知识整理

0ziyu0 2019-02-20 20:45 原文

Java多线程中相关术语:

  一、Java多线程原则

    1.原子性:一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。一般使用线程同步或者Lock锁来确保。

    2.可见性(Java内存模型):当多个线程同时访问一个变量时,其中某个线程改变改变量值,其他线程需要立即查询到。

    3.有序性(涉及JVM对代码的重排序问题):编译器和处理器对不影响逻辑的代码进行重排序(执行顺序),以便提高运行速率(涉及Java的内存屏障,可以使用volatile来禁止重排序)。

              备注:指令重排序时不能把后面的指令重排序到内存屏障之前的位置。

   二、Java内存模型(JMM)

    1.JMM决定了一个线程对共享变量进行操作后,能否对其他线程可见。

    2.JMM为每个线程抽象出一个本地内存概念,让一个线程对共享变量进行修改时,先修改本地内存中的值,然后在将本地内存中的值刷新到主线程中供其他线程读取。

       其中volatile关键字修饰的变量,会被立即更新到主内存中,供其他线程所调用。

       备注(volatite的作用域):在Java中为了加快程序的运行效率,对变量的操作是在寄存器或者CPU上,之后在同步到主内存中,使用volatile修饰后会立即同步至主内存中。   

                                           

 

Java 中的两种线程:

  1. 守护线程 : 主线程停止后,守护线程会立即停止( 例如:GC 线程) Java 中使用setDaemon(true) 方法来设置为守护线程。
  2. 用户线程 : 用户自定义的线程,当主线程停止后,用户不会立即停止。

Java 中多线程的几个相关状态:

  新建状态、就绪状态 ( 调用start 方法后) 、运行状态、阻塞状态及死亡状态。

    

 

Java 多线程中常用的方法:

  join(): 将执行权让给被调用方。

  setPriority(): 设置获取CPU 调度优先级。

  yield():放弃当前CUP 执行权, 和其他线程在一次竞争CPU 使用权。

  wait():停止当前执行的线程,释放锁让其他线程获取该锁。

  notify():唤醒因锁而阻塞的线程。

  注意:wait和notify要与synchronized一起使用,sleep方法不会释放锁。

    使用wait和notify实现消费者和生产者(同步方式)[代码示例:wait和notify实现]

 

 

 Java多线程中锁的分类:

  重入锁:同一个线程中,函数之间调用或者递归调用,锁是可传递的,也称递归锁。特点:1.当程序进入同步代码快时自动获取锁,程序执行完毕后或者有异常抛出时自动释放锁。2.该锁是互斥锁。3.使用synchronized修饰[代码示例1-1]。

                  synchronized是重量级锁,Lock中ReentrantLock是轻量级锁,使用时需要手动释放锁。

  注意:1.同步方法使用的是this锁。2.静态同步函数采用的锁是字节码文件对象(类.class)。

  读写锁:允许多个线程同时读同一个共享资源时,一旦有写操作时将禁止其他线程读取(读-读可共存,读-写不共存,写-写不共存),适用于对共享资源写操作少

      ReentrantReadWriteLock[代码示例:读写锁实现]

  乐观锁:思想是默认认为不会发生并发问题,每次取数据时认为没有其他线程对数据进行修改过,但在更新数据时使用版本号或者CAS方式判断在自己保存之前是否有数据被更改过,如果没有则保存,若果已经被修改则重试。

            版本号(version)方式:在获取数据的表中添加一个version字段,标注当前数据被更新的次数,在更新数据之前先查询version值,在保存时再次查询version值是否被更改,一直重试到当前version和数据库一致才能保存。

                                                   eg: update table set x = x +1, version = version + 1 where x = #{x} and version = #{version};

       CAS(compare and swap)方式:有3个关键词,主内存值(V):,预期值:本地内存(E),新值:更新后的值(N)

                   当V=E(主内存值等于本地内存值),表示数据没有被修改过,此时可以进行变更。

                   当V!=E,表示数据被其他线程修改过,此时重新将主内存中的值刷新到本地内存中,然后再重新比较。 

                   jdk底层源码案例[CAS源码] 

    CAS缺点:无法解决ABA问题,即一个线程先将值由A改成了B,然后里一个线程在由B改成了A,此时C线程会当做A没有被修改过。可以使用AtomicStampedReference类解决(原理是用时间戳记录).

    jdk提供常用的实现了CAS的类:AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等

  悲观锁:在操作数据时,认为每次都会有其他线程去修改,所以读取都会被加锁。

  分布式锁:缓存实现(Redis)、Zookeeper实现等。

 

Java多线程解决方案:

  ThreadLocal:为每个线程提供一个自己的局部变量(隔离共享变量)。

       ThreadLocal方法简介:

          void set(Object value) 设置当前线程的线程局部变量的值。

    Object get() 返回当前线程所对应的线程局部变量。

         void remove()将当前线程局部变量的值删除(线程执行完毕后,jdk将自动回收ThreadLoacl的局部变量)[代码示例2-1]。

        ThreadLocal实现原理:底层采用Map进行封装,key为当前线程,value为需要保存的值。

 

              

 

 

 

=================================================================================

多线程使用不当导致的问题集锦:

  1.wait和notify实现。

 

class Resource {

    public String name;
    public Integer sex;
    public boolean isCustomer = false;
}
// 生产者
class Produces extends Thread {

    private Resource resource;

    Produces(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        int count = 0;
        while(true) {
            synchronized (resource) {
                if(resource.isCustomer) {
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(count == 0) {
                    resource.name = "produce";
                    resource.sex = 0;
                } else {
                    resource.name = "cusoumer";
                    resource.sex = 1;
                }
                count = (count + 1) % 2;
                resource.isCustomer = true;
                resource.notify();
            }
        }
    }
}
// 消费者
class Cusoumers extends Thread {

    private Resource resource;

    Cusoumers(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (resource) {
                try {
                    if(!resource.isCustomer) {
                        resource.wait();
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("当前消费:" + resource.name + "  " + resource.sex);
                resource.isCustomer = false;
                resource.notify();
            }
        }
    }
}

public class WaitAndNotify {

    public static void main(String[] args) {
        
        Resource resource = new Resource();
        
        Produces produce = new Produces(resource);
        Cusoumers cusoumer = new Cusoumers(resource);
        produce.start();
        cusoumer.start();
        
    }
    
}

 

2.当在使用多线程时,线程间同时对某个共享的全局变量或者静态变量进行写操作时,将会发生数据冲突问题。

eg(1-1):

 1 @SuppressWarnings("static-access")
 2 public class MultiThread implements Runnable {
 3 
 4     private Integer count = 100;
 5     
 6     @Override
 7     public void run() {
 8         
 9         while(count > 0) {
10             try {
11                 Thread.currentThread().sleep(100);
12             } catch (InterruptedException e) {
13                 e.printStackTrace();
14             }
15             sale();
16         }
17     }
18     
19     private void sale() {
20         if(count > 0) {
21             System.out.println("第" + (100 - count + 1) + "张票售出");
22             count--;
23         }
24     }
25 
26     public static void main(String[] args) {
27         
28         MultiThread multiThread = new MultiThread();
29         
30         Thread m1 = new Thread(multiThread, "1号窗口");
31         Thread m2 = new Thread(multiThread, "2号窗口");
32         
33         m1.start();
34         m2.start();
35     }
36     
37 }

使用内置锁来处理(不推荐,性能比较低)

1 private void sale() {
2         synchronized (this) {
3             if(count > 0) {
4                 System.out.println("第" + (100 - count + 1) + "张票售出");
5                 count--;
6             }
7         }
8     }

 

2.ThreadLocal

eg(2-1):

class Produce{
    
    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            
            return 0;
        }
    };
    
    public Integer getCount() {
        
        Integer nextCount = threadLocal.get() + 1;
        threadLocal.set(nextCount);
        
        return nextCount;
    }
    
}

public class ThreadLocalTest extends Thread {

    private Produce produce;
    
    ThreadLocalTest(Produce produce) {
        this.produce = produce;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "___" + produce.getCount());
        }
    }
    
    public static void main(String[] args) {
        
        Produce produce = new Produce();
        ThreadLocalTest t1 = new ThreadLocalTest(produce);
        ThreadLocalTest t2 = new ThreadLocalTest(produce);
        ThreadLocalTest t3 = new ThreadLocalTest(produce);
        t1.start();
        t2.start();
        t3.start();
        
    }
    
}

 编译器对代码进行重排序

class Reorder {

    int a = 0;
    boolean flag = false;

    public void writer() {
        a = 1; // 当线程执行是a=1和flag=true时,编译器可能会先执行flag=true,然后在执行a=1
        flag = true;
    }

    public void reader() {
        int i = 0;
        if(flag) {// 当线程执行是a=1和flag=true时,编译器可能会先执行i=a*a,然后在执行if(flag)
            i = a * a;
            System.out.println(i);
        }
    }
}

class ReorderThread1 extends Thread {

    private Reorder reorder;

    ReorderThread1(Reorder reorder) {
        this.reorder = reorder;
    }

    @Override
    public void run() {
        reorder.writer();
    }

}

public class ReorderThread extends Thread {

    private Reorder reorder;

    ReorderThread(Reorder reorder) {
        this.reorder = reorder;
    }

    @Override
    public void run() {
        reorder.reader();
    }

    public  static  void main(String[] args) {

        Reorder reorder = new Reorder();
        ReorderThread t1 = new ReorderThread(reorder);
        ReorderThread1 t2 = new ReorderThread1(reorder);
        
        t1.start();
        t2.start();


    }
}

 3.读写锁

 1 public class ReadWriteLockTest {
 2 
 3     public static Map<Integer, Integer> maps = new HashMap<>();
 4     public static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 5 
 6     public static Lock readLock = readWriteLock.readLock();
 7     public static Lock writeLick = readWriteLock.writeLock();
 8 
 9 
10     public static Integer getResult(Integer key) {
11 
12         readLock.lock();
13         Integer result = null;
14         try {
15             System.out.println("=======读=======");
16             Thread.currentThread().sleep(50);
17             result = maps.get(key);
18             System.out.println(key);
19             System.out.println("=======读结束=======");
20         } catch (Exception e) {
21             e.printStackTrace();
22         } finally {
23             readLock.unlock();
24         }
25 
26         return result;
27     }
28 
29     public static void putResult(Integer key, Integer value) {
30 
31         writeLick.lock();
32         try {
33             System.out.println("=======写=======");
34             Thread.currentThread().sleep(50);
35             maps.put(key, value);
36             System.out.println(key + "_" + value);
37             System.out.println("=======写结束=======");
38         } catch (Exception e) {
39             e.printStackTrace();
40         } finally {
41             writeLick.unlock();
42         }
43 
44     }
45 
46     public static void main(String[] args) {
47 
48         new Thread(new Runnable() {
49 
50             @Override
51             public void run() {
52                 for (int i = 0; i < 100; i++) {
53                     putResult(i, i);
54                 }
55             }
56         }).start();
57 
58         new Thread(new Runnable() {
59 
60             @Override
61             public void run() {
62                 for (int i = 0; i < 100; i++) {
63                     getResult(i);
64                 }
65             }
66         }).start();
67     }
68 }

 

若屏蔽掉读写锁,如下

3. CAS源码

 1 public final int getAndAddInt(Object o, long offset, int delta) {
 2         int v;
 3         do {
 4             v = getIntVolatile(o, offset);
 5         } while (!compareAndSwapInt(o, offset, v, v + delta));
 6         return v;
 7     }
 8 
 9 /** 
10      * Atomically increments by one the current value. 
11      * 
12      * @return the updated value 
13      */  
14     public final int incrementAndGet() {  
15         for (;;) {  
16             //获取当前值  
17             int current = get();  
18             //设置期望值  
19             int next = current + 1;  
20             //调用Native方法compareAndSet,执行CAS操作  
21             if (compareAndSet(current, next))  
22                 //成功后才会返回期望值,否则无线循环  
23                 return next;  
24         }  
25     }  

 

推荐阅读