之前都是使用Synchronized来实现锁功能,java 1.5之后增加了lock接口来增强锁的功能;lock提供的新功能有尝试非阻塞式获取锁、能被中断获取锁、超时获取锁。方法有:1、lock() 2、lockInterruptibly() 3、tryLock() 4、unlock() 5、newCondition()等方法;
锁的功能实现基于队列同步器AQS,通过内置的FIFO队列来完成资源获取线程的排队工作;同步器的三个基本方法getState()、setState(int newstate)、compareAndSetState(int expect,int update)(通过CAS设置当前状态,保证原子性);同步器在获取锁时提供了一些模板方法去获取和释放锁或者查询线程状态acquire()、acquireShared()、release()等。AQS内置的FIFO队列是一个双向队列,其队列的每一个节点都是一个线程节点的封装,包含线程状态、前节点、后继节点等信息,同步器中也有head和tail的成员变量。所以一个锁的具体实现本质就是在不断的进行队列的出队和入队操作,没有获得锁便把这个节点设置为同步队列的尾节点,这个入队需要CAS设置,因为有线程安全的问题。首节点是获得锁的节点,它在释放锁的同时会唤醒后继节点。同步队列中的所有线程节点其实都处于不断的自旋过程(是否能够CAS设置成功)。(这里有个公平锁与非公平锁的区别,公平锁在获取锁之前还有检查它的前继节点是否为首节点)。同时锁的实现也分独占锁和共享锁,下面以可重入锁和读写锁的实现为例。可重入锁保证同一个线程可以多次持有某个锁,不会发生让自己阻塞自己的情况,互斥锁Mutex不可重入;典型应用是一个带同步的递归函数。
可重入锁又分为公平锁和不公平锁,公平锁会减少饥饿现象,但是比不公平锁都产生大量的线程切换,效率较低。可重入锁和读写锁维护的线程状态将是一个大于等于0的整型,代表持有锁的个数,只有变为0,才表示完全释放锁。读写锁维护一个32bit的二进制,高16位表示读状态,低16位表示写状态。
LockSupport工具,可以对线程实现管理,park()、unpark(),等静态方法。
任何Object的锁对象都有一组监视器方法用于通信,但是对于lock来实现的同步,并非以资源对象为监视器的,而是自定义的重入锁或者读写锁等实现类,这会不具备通信的方法。所以java提供了Condition接口,用于实现通知/等待功能,signal()、await()等方法,Condition的具体实例可以直接由Lock实例的newCondition()获取,其构造是在锁的实现上改进,一个condition代表一个等待队列,await()表示加入该Condition的等待队列,signal()表示从等待队列中移动到同步队列,从而退出await()的循环函数。