本文在参考java并发编程实战后完成,参考内容较多java
锁是用来控制多线程访问共享资源的方式,一个锁可以防止多个线程同事访问共享资源。在Lock
接口出现以前,Java程序是经过synchronized
来实现锁功能的,在JDK1.5以后,新增的Lock
接口能够实现锁功能,他的功能与Synchronized
相似,可是须要显式的获取和释放锁,他失去了隐式获取释放锁的便捷性,可是可操做性更强,同时具备可中断获取锁以及超时获取锁的特性。编程
Lock
接口提供了几个synchronized
不具有的主要特性:安全
Lock
是一个接口,定义以下:多线程
package java.util.concurrent.locks; import java.util.concurrent.TimeUnit; /** * @see ReentrantLock * @see Condition * @see ReadWriteLock * * @since 1.5 * @author Doug Lea */ public interface Lock { /** * 线程获取锁,若是获取锁失败,线程没法向下执行 */ void lock(); /** * 可中端的获取锁,和lock()相比这个方法能够响应中断,就是在获取锁的过程当中能够中断当前线程 */ void lockInterruptibly() throws InterruptedException; /** * 尝试非阻塞的获取锁,调用该方法后马上返回,获取锁成功返回true,不然返回false */ boolean tryLock(); /** * 超时的获取锁,在发生下面的状况下会返回: * 一、在超时时间范围内获取锁成功马上返回。二、当前线程在超时时间内被中断 三、超时时间结束,返回false */ boolean tryLock(long time, TimeUnit unit) throws InterruptedException; /** * 释放锁 */ void unlock(); /** * 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的wait方法,调用后,当前线程将释放锁。 */ Condition newCondition(); }
队列同步器
AbstractQueuedSynchronizer
,是构建锁或者其余同步组件的基础框架,他使用int成员变量表示同步状态(这个同步状态在不一样的同步组件中表示的含义会有差别),经过内置的FIFO队列完成线程获取资源的排队工做。
同步器使用的主要方式是经过继承并实现它定义的抽象方法来管理同步状态。队列同步器提供了操做同步状态的方法,能够保证状态的修改是线程安全的,同步器支持独占的获取同步状态,也支持共享式的获取。并发
同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。锁是面向使用这的,他定义了使用者与锁交互的接口,隐藏了实现细节;同步器面向的是锁的实现者,他简化了锁的实现方式,屏蔽了同步状态管理、线程排队,等待唤醒等底层操做。
AbstractQueuedSynchronizer
的定义:框架
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable
//同步器提供的修改活访问状态的方法性能
//获取当前同步状态 protected final int getState() { return state; } //设置当前同步状态 protected final void setState(int newState) { state = newState; } //使用CAS设置当前状态,能够保证设置状态的原子性 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
同步器的设计是基于模版方法模式的,使用者须要继承同步器并重写指定的方法,随后将同步器组合在自定的同步组件实现中,并调用同步器提供的模版方法,而这些模版方法将会调用使用者重写的方法ui
ReentrantLock
ReentrantLock
是Java中提供的另外一种锁的实现.他经过调用lock()
方法获取锁;调用unlock()
方法释放锁。ReentrantLock
的实现依赖于Java同步框架AbstractQueuedSynchronizer
,AQS使用一个整形的volatile变量(命名为state)来维护同步状态,volatile变量是ReentrantLock内存语义实现的关键。this
ReentrantLock
分为公平锁和非公平锁。ReentrantLock的类图以下:spa
ReentrantLock实现了Lock接口,内部有三个内部类,Sync、NonfairSync、FairSync,Sync是一个抽象类型,它继承AbstractQueuedSynchronizer,这个AbstractQueuedSynchronizer是一个模板类,它实现了许多和锁相关的功能,并提供了钩子方法供用户实现,好比tryAcquire,tryRelease等。Sync实现了AbstractQueuedSynchronizer的tryRelease方法。NonfairSync和FairSync两个类继承自Sync,实现了lock方法,而后分别公平抢占和非公平抢占针对tryAcquire有不一样的实现。
首先分析公平锁:ReentrantLock lock = new ReentrantLock(true);
公平锁声明,若是不为true,或者使用默认,那么是非公平锁。
加锁lock()方法的调用轨迹以下:
第4步是加锁的关键步骤:
protected final boolean tryAcquire(int acquires) { //获取当前尝试获取锁的线程 final Thread current = Thread.currentThread(); //获取AQS中的volatile变量state int c = getState(); if (c == 0) { //判断队列以前是否有其余的线程在等待 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //设置锁的持有者为当前线程 setExclusiveOwnerThread(current); return true; } } //若是当前线程已经获取了锁,那么进行锁重入 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
CAS + volatile 构成了AQS以及原子变量类。
JDK文档对CAS的说明:若是当前状态值与预期值相等,则以原子的方式讲同步状态设置为给定的更新值,此操做具备volatile读和写的内存语义。这意味着编译器不能对CAS与CAS前面和后面的任意内存操做重排序。CAS操做对应的本地方法最终对应的处理器源代码回有一个Atomic::cmpxchg
指令。程序回根据当前处理器的类型来决定是否为cmpxchg
指令添加lock前缀。若是是多处理器上运行,那么添加Lock前缀,若是是单处理器那么就会省略。lock前缀会确保内存的读写改操做原子执行。(但处理器自身会维护)
【补充volatile的内存语义:一、在程序中,当第一个操做为普通变量的读或写时,若是第二个操做为volatile写,则编译器不能重拍下这两个操做 二、当第二个操做为volatile写时,无论第一个操做是什么,都不能重排序。这个规则确保volatile写以前的操做不会被编译器重排序到volatile写以后 三、当第一个操做是volatile读时,无论第二个操做是什么,都不能重排序,这个规则确保volatile读以后的操做不会被编译器重排序到volatile读以前 四、当第一个操做是volatile写,第二个操做是volatile读时,不能重排序】
ReentrantLock是支持可重入的排他锁,这些锁在同一时刻只容许一个线程进行访问,而读写锁在同一时刻能够容许多个读线程访问,可是在写线程访问时,全部和读线程和其余的写线程均被阻塞。读写锁维护了两个锁,一个读锁,一个写锁,经过读写锁,能够提升兵法性能。
Java中提供的读写锁的实现是ReentrantReadWriteLock
,提供的特性以下: