不懂并发?看完就超神!!!Java并发基础(二)

wait、notify

在这里插入图片描述
wait和notify调用的前提是它们在调用之前是owner,如果在没有获得锁就调用会报错(所以需要使用syn)。
在这里插入图片描述
一般使用motifyall,因为notify不能指定唤醒会造成虚假唤醒。

● wait(),notify(),notifyAll() 和 synchonized 需要搭配使用, 用于线程同步
● 线程被notify后,会从wait的地方开始继续执行。
● 当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁,然后该线程会被添加到该对象的等待队列中(waitset), 只要该线程在等待队列中, 就会一直处于blocked状态, 不会被调度执行。
● 都属于object类。
● 当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waitset)的线程中取出任意一个线程,将其添加到入口队列( entrylist) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。如果等待队列中没有线程, notify()方法不会产生任何作用。
● notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting
queue)中所有的线程都添加到入口队列中(entry queue)。
● 注意, notifyAll()比notify()更加常用, 因为notify()方法只会唤起一个线程,
且无法指定唤醒哪一个线程,所以只有在多个执行相同任务的线程在并发运行时, 我们不关心哪一个线程被唤醒时,才会使用notify()。

sleep、yeild

● sleep单位毫秒,sleep内处于blocked状态,sleep完后线程进入就绪状态,sleep不会释放锁,属于thread类方法,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会,同时sleep完后不一定就马上执行,还需要看CPU的执行以及线程的优先级。
● 其他线程可以调用正在sleep线程的interupt方法打断sleep,sleep会抛出一个Intertupt的异常。
● yield和sleep一样都是暂停当前正在执行的线程对象且同为thread类方法,不会释放资源锁。
● yeild和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行的时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

模式-保护性暂停

在这里插入图片描述
在这里插入图片描述

join原理

join底层基于保护性暂停模式。
假设线程A调用线程B的join
millis为0时就会一直调用wait等待,直到B not alive。
当millis不为0时,等待的delay时间已经超过时就不会再继续调用wait,如果delay还有时间就继续调用wait(delay)继续等待。
在这里插入图片描述

异步模式

生产者消费者模式

在这里插入图片描述

park、unpark

在这里插入图片描述

特点

在这里插入图片描述
先调用unpark,在下次调用park时就不会blocked。

原理

每个线程都有一个Parker对象,有三部分组成 _counter、 _cond 、_mutex。
在这里插入图片描述

  1. 当前线程调用park方法。
  2. 检查counter,为0,获取mutex,为1时继续执行。
  3. 线程进入cond blocked。
  4. 设置counter为0。

在这里插入图片描述

  1. 调用unpark方法,设置counter为1。
  2. 唤醒cond中的线程thread0,没有就不管。
  3. thread0恢复执行。
  4. htread0将count设为0。

死锁

  1. 互斥:在一段时间内,某资源只能被一个进程占用,此时其他进程请求该资源时,只能等待到资源被使用完后释放。
  2. 请求和保持:一个进程已经至少拥有了一个资源,但它又需要其他资源,这些资源又被其他进程拥有,同时它们对自己已拥有的资源不释放,相互一直等待。
  3. 不可抢占:资源不可抢占。
  4. 循环等待条件,必然存在一个进程等待另一个进程的死循环。

哲学家就餐问题
在这里插入图片描述

活锁

活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
生活中的典型例子: 两个人在窄路相遇,同时向一个方向避让,然后又向另一个方向避让,如此反复。
解决协同活锁的一种方案是调整重试机制,一种是加入随机性或调整重试次数等。

饥饿

饥饿 ,与死锁和活锁非常相似。是指一个可运行的进程尽管能继续执行,但被调度器无限期地忽视,而不能被调度执行的情况,如抢占式调度中低优先级的进程可能永远获取不到CPU。

ReentrantLock

可打断

使用ReentrantLock的lock方法为不可打断的锁(与syn一样一直会等待锁,直到拿到锁后执行完),而使用lockinterruptibly就支持可打断(让其不再等待下去),当又竞争时就会进入blocked,直到其他线程调用其interrupt又继续运行(本质就是避免一直死等锁)。

锁超时

使用trylock方法可以指定等待时间,也是为了避免死等,如果不指定时间就是立马返回结果。

公平锁

syn就是不公平锁,当在entrylist中不会按照到达的顺序来,而是竞争,公平锁就是指在entrylist中会按照到达的顺序来选择owner,先到先得。
在这里插入图片描述
可以指定是否创建公平或者非公平锁。

条件变量

syn只有一个条件变量既waitset,而reentrantlock就可以定义多个条件变量,这样唤醒就可以唤醒指定condition内的线程。其中signal,signalall对应notify和notifyall,await和wait的使用条件类似。

JMM

在这里插入图片描述
在这里插入图片描述

可见性

在这里插入图片描述
初始时线程t读取run的值到自己的工作内。
在这里插入图片描述
由于t的频繁读取run,JIT编译器会将run的值缓存在t的工作内存中,提高效率。
在这里插入图片描述
而这时主线程读取run的值,并将run的值修改为false,但是t任然在读取自己工作内存中的run值,于是导致结束不了,这就是可见性。
在这里插入图片描述

原子性

原子性就是指指令受线程上文切换的影响,由于这个操作不是原子操作,而是被分为几步执行,在多线程下就会出现问题。

volatile就只能保证可见性,无法保证原子性。

有序性

指令重排:JVM在不影响正确性的前提下会将指令排序优化,这在多线程下会导致问题。
有三种情况:r1=1、r1=4、r1=0

volatile底层原理

保证可见性
volatile可以使得线程避免从自己的工作缓存中查找变量的值,而是到主存中获取,只能修饰成员变量和静态成员变量。
在这里插入图片描述
写屏障:在该屏障前,对共享的改动都会同步到主存中,这样保证写的操作都会马上写入主存。
在这里插入图片描述
读屏障:在该屏障后,对数据的读取是主存中的最新数据。
在这里插入图片描述

保证有序性
写屏障会防止jvm将写屏障之前的代码排在写屏障后面。
在这里插入图片描述
读屏障会防止jvm将读屏障后的代码排在读屏障前面。
在这里插入图片描述

不能保证原子性
但是不能解决指令交错的问题
在这里插入图片描述

dcl中的volatile

在这里插入图片描述
如果不加第一次的checked,每次调用getinstance都会去使用syn,syn是重量级锁,加上第一次的checked就可以保证在instance为null时再去执行syn,可以使得只需要在第一次读取instance读取。

但是由于第一次checked没有加上syn就不能保证原执行和可见性,同时syn本身也不能保证有序性(只有当instance完全被syn包含才能保证)只能保证原子和可见性以及特定情况下的有序性,而Instance = new Singleton() 时,实际上是分为三步走的:

  1. 为Instance在栈中分配内存空间。
  2. 调用构造器,实例化instance。
  3. 将Instance指向分配的内存地址既赋值给instance。

JVM指令重排的特性,可能使得顺序不一样,2有可能在执行1、3后,另外一个线程调用getinstance,这时instance已经不为空了,于是就返回,但实际上instance还没有实例化。

public class Singleton {
    
         
   private static volatile Singleton instance;   
   private Singleton (){
    
    }        
   public static Singleton getInstance() {
    
         
        if(instance == null){
    
    
              synchronized(Singleton.class){
    
    
                   if(instance == null){
    
    
                       instance = new Singleton();
                   }
              }
        }
        return instance;     
   }  
}

加上volatile就可以避免,12必须在3前,由于volatile不能保证原执行,那么其他线程可能会在123之前读取,这样下面的syn就起到保证原子性的作用了,进入后又checked第二遍时发现不为null了。

无锁并发

CAS和volatile

compare and set 一个原子操作。
在这里插入图片描述

CAS特点 乐观与悲观

cas经常和while配合使用。
在这里插入图片描述

ABA问题

此时A可能已经不是原来的A了,但是CAS任然返回true,在大部分情况下虽然不会有问题,但是任存在隐患。

AtomicStampedReference

使用stamp,每次对共享变量改变时,都会将stamp加1,这样每次在CAS时也会验证stamp是否是自己读取时的stamp,相同就表示没有修改过,不同就直接返回false,解决ABA问题。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/TheCarol/article/details/112997826