虽然都是用来实现锁功能,但是lock需要显式的获取和释放锁,而synchronized是隐式的获取和释放锁,虽然synchronized用起来比较方便,但lock能够以非块结构来实现互斥同步,更加自由。
同步器
队列同步器是lock如何完成线程同步的关键,同步器依赖内部的一个同步队列(FIFO的双端队列)来完成同步操作。
具体来说,就是如果当前线程获取同步状态失败(也就是获取锁失败),同步器就会把当前线程和等待状态等信息构造成一个节点(Node),并把这个节点加入同步队列,并阻塞这个线程。同步器里面有两个指针,一个head,一个tail,分别指向队列头和队列尾,队列的头结点表示获得同步状态成功的节点。同步器的构造如下:
这个过程中重点是设置头结点和尾节点的操作细节:
1、设置尾节点(对应lock操作)
当一个线程获得同步状态之后,其他的线程因为无法获得同步状态,因为会被包装成一个节点加入队列尾,但是因为这个过程是并发的,因此加入队列尾的过程需要保证线程安全。因此,同步器提供了一个基于循环CAS来设置队列尾的方法。原理就是CAS。成功后,就会将原来的tail和这个节点利用指针完成关联,并把tail改成该节点:
2、设置头结点(对应unlock操作)
而设置首节点这个操作则是原本获得同步状态(也就是获得锁)的线程来完成的,由于只有一个线程能获得同步状态,因此这个过程是单线程的,不存在线程安全问题,也就不需要CAS操作。他只需要将首节点设置成他的后继结点,然后把后它的next和后继的pre指针改一下即可。
等待通知模式的实现
之前sychronized里面利用Object类的方法wait和notify来实现了等待通知模式,Condition接口也提供了类似的方法,用来和Lock类配合实现等待通知。
Condition对象是由Lock对象创建出来的,使用Lock类的newCondition方法创建。调用condition的await方法,会释放锁并进行等待,然后等待其他线程调用condition的signal方法通知本线程,等待其他线程释放锁之后在获得锁继续执行。
condition具体实现:等待队列
每个condition对象都包含一个等待队列,await()和signal()方法的实现其实本质上是等待队列里面的Node的入队列和出队列的操作(从同步队列入,出到同步队列)。
对于一个Synchronized来说,一个对象有一个同步队列,和一个等待队列,而Lock则有一个同步队列和多个等待队列。因为它可以有多个condition对象。结构如下:
每个condition都是同步器的内部类,或者说他都有所属的同步器的引用。
1、await()操作
执行await方法时,意味着当前线程一定有condition相关联的锁,因此首先会把同步队列的首节点取出,并构造成一个新节点,加入到condition的等待队列的末尾,同时释放锁,唤醒同步队列里面的后继结点,让他成为首节点,并让当前线程进入等待状态。
2、signal()操作
首先判断当前线程是否获得了锁,如果获得了,那么取出等待队列里里面的首节点,然后把这个节点构造成新节点放入同步队列尾。来竞争锁,如果成功获得了锁,那么就会从之前的await方法返回。而所谓的signalAll()表示将等待队列里面的所有节点全部移到同步队列里面去。