在学习本文之前,我们来思考一个问题,思考: 如何设计一把独占锁?
1. 首先,我们可以定义一个变量state,标识加锁状态。0表示没有加锁,1表示已加锁。在并发场景下,会出现多个线程同时调用加锁方法,操作state变量(将state由0改为1),为了保证只有一个线程能够修改成功,可以使用CAS来实现。其它线程会修改失败,对于修改失败的线程,意味着竞争锁失败;
2. 对于竞争锁失败的线程怎么办呢?设置一个等待队列,用于存放竞争锁失败要等待的线程,这些线程会被阻塞。当持有锁的线程释放锁之后,可以唤醒等待队列中的线程,出队,去竞争锁;
3. 线程在拿到锁执行的过程中,发现所需的某些条件不满足,需要等待另外的线程把条件满足了才能继续往下执行。这个过程要阻塞、唤醒线程,又要用到等待通知机制。等待通知机制可以使用synchronized + obj.wait()/obj.notify()/obj.notifyAll() ,而这种方式显然会有问题:不能唤醒指定的线程,或者调用...All()方法会把所有的线程都唤醒。我们需要等待唤醒指定的某个线程,使用LockSupport.park/unpark;
4. 在实现独占锁、共享锁时,对于一些共性的东西,如:入口等待队列和条件等待队列,入队、出队、CAS操作这些属于共性操作,在java中针对这些公共逻辑,可以使用一种设计模式-模板方法模式实现。因此,可以定义个抽象类。于是,AQS就被设计出来了;
等待通知机制:
synchronized + obj.wait()/obj.notify()/obj.notifyAll()
ReentrantLock + condition.await()/condition.signal()/condition.signalAll()
这两种等待唤醒机制底层用到的阻塞、唤醒线程的API都是park/unpark。ReentrantLock + condition.await()/condition.signal()/condition.signalAll()里面用到的是LockSupport.park/unpark。synchronized + obj.wait()/obj.notify()/obj.notifyAll()底层也是用的park/unpark,只不过是在JVM层面对应的API park/unpark。
补充:线程的调度是由操作系统来实现的,Java中的线程和操作系内核线程是建立了1:1的对应关系, 也就是说Java 线程都是直接映射到一个操作系统原生线程来实现的。所以,要阻塞线程最终去调操作系统提供的接口去完成。例如,Linux操作系统中有一个线程库pthead。
1. 管程 — Java同步的设计
管程:指的是管理共享变量以及对共享变量的操作过程,让他们支持并发;
互斥:同一时刻只允许一个线程访问共享资源;
同步:线程之间如何通信、协作;
MESA模型
在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。