一、锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时
访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。在Lock接
口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增
了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功
能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提
供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以
及超时获取锁等多种synchronized关键字所不具备的同步特性。
特性 | 描述 |
非阻塞地获取锁 | 当前线程尝试获取锁,如果这时刻没有被其他线程获取到,则成功获取锁 |
能被中断地获取锁 | 与synchronize不通过,获取到锁地线程能够响应中断,获取到锁地线程被中断时,异常抛出,释放锁, |
超时获取锁 | 指定地截至时间之前获取锁,若截至时间到了,无法获取
|
二、 队列同步器
1、AbstractQueuedSynchronizer(队列同步器)是用来构建锁或者其他同步组
件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获
取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。
同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状
态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3
个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操
作,因为它们能够保证状态的改变是安全的。子类推荐被定义为自定义同步组件的静态内部
类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来
供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获
取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、
ReentrantReadWriteLock和CountDownLatch等)。
同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步
器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交
互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,
它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同
步器很好地隔离了使用者和实现者所需关注的领域。
三、同步队列
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取
同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其
加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再
次尝试获取同步状态。
同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和
后继节点。
同步器包含了两个节点类型的引用,一个指向头节点,而另一个指向尾节点。
试想一下,当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,转
而被构造成为节点并加入到同步队列中,而这个加入队列的过程必须要保证线程安全,因此
同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Node expect,Node
update),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式
与之前的尾节点建立关联。
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态
时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。
设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能
够成功获取到同步状态,因此设置头节点的方法并不需要使用CAS来保证,它只需要将首节
点设置成为原首节点的后继节点并断开原首节点的next引用即可。