前序
synchronized的不足之处
之前我们介绍过Java原生的锁——基于对象的锁。它一般配合synchronized关键字使用。不过synchronized关键字也存在一些不足:
(1)如果只是对临界区进行读操作的话,是可以多线程一起执行的。但synchrnoized只允许在同一时间内只有一个线程执行临界区。
(2)synchrnoized无法知道线程有没有成功获取锁。
(3)当拿到对象锁的线程因为临界区的IO或者sleep方法等原因阻塞了,它是不会释放锁的,这会导致其他线程等待。
Java在JUC包下提供了一些关于锁的类和接口,上述问题都是可以通过JUC包中的锁来解决的。
锁的几种分类
可重入锁和非可重入锁
可重入锁是指当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。synchrnoized关键字就是重入锁。
我们在继承AQS中实现同步器时,需要考虑到占有锁的线程还会再次获取锁的场景,否则可能会导致线程阻塞,即非可重入锁。ReentrantLock就是非可重入锁。
公平锁与非公平锁
公平锁是指按照锁的申请顺序来获取锁,反之则是非公平锁。ReentrantLock支持非公平锁和公平锁两种;synchronized是非公平锁。
公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒线程的开销比非公平锁要大。
一般情况下,非公平锁能提升一定的效率,因为线程有几率不阻塞直接获得,这样可以减少唤起线程的开销,整体的吞吐效率高。但可能会发生线程饥饿(有一些线程长时间得不到锁)的情况。。
读写锁和排他锁
synchronized关键字和ReentrantLock类都是排他锁,即同一时刻只允许一个线程访问;而读写锁可以在同一时刻允许多个线程访问,不过当写线程访问时,所有的读线程和其他写线程均被阻塞。
Java提供了ReentrantReadWriteLock类作为读写锁的默认实现,内部维护了两个锁:一个读锁,一个写锁。通过分离读锁和写锁,使得在“读多写少”的环境下,大大地提高了性能。
AQS
队列同步器AbstractQueuedSynchronizer(简称AQS)是用来构建锁或其他同步组件的基础框架。我们遇到的ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS,因此我们也可以利用AQS来构造出符合我们自己需求的同步器。
属性
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
static final class Node {
/*
等待状态waitStatus有几个枚举值:
(1)0 当一个Node被初始化的时候的默认值
(2)CANCELLED 为1,表示线程获取锁的请求已经取消了
(3)CONDITION 为-2,表示节点在等待队列中,节点线程等待唤醒
(4)PROPAGATE 为-3,当前线程处在SHARED情况下,该字段才会使用
(5)SIGNAL 为-1,表示线程已经准备好了,就等资源释放了
*/
volatile int waitStatus; //当前节点在队列中的状态
volatile Node prev;
volatile Node next;
volatile Thread thread; //处于该节点的线程
Node nextWaiter;
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
}
AQS内部使用了一个volatile的变量waitStatus来作为资源的标识。同时定义了几个获取和改版state的protected方法,子类可以覆盖这些方法来实现自己的逻辑:
getState() //获取当前同步状态。
setState() //设置当前同步状态。
compareAndSetState() //使用CAS设置当前状态,该方法能够保证状态 设置的原子性。
这三个方法均是原子操作,其中compareAndSetState()方法的实现依赖于Unsafe的compareAndSwapInt()方法。
AQS类本身实现的是一些排队和阻塞的机制,它内部使用了一个先进先出(FIFO)的双端队列,并使用了两个指针head和tail用于标识队列的头部和尾部。当有线程加入队列时,该过程需要保证线程安全,因此同步器提供了基于CAS的置尾节点的方法compareAndSetTail(Node expect,Node update),该方法的两个参数分别是当前线程预期的尾节点和当前节点。

队列中的首节点是获取同步状态成功的节点,在它释放同步状态后,会唤醒后继节点,而后继节点在获取同步状态成功时将自己设置为首节点。设置首节点是由成功获取同步状态的线程来完成的,由于只有一个线程能成功获取,因此设置头节点的方法并不需要CAS来保证。
它是一个抽象类,只实现一些主要逻辑,有些方法由子类实现;

除此之外,同步器也实现了一些模板方法:

在AQS里,资源有两种共享模式(同步方式):
- 独占模式(Exclusive):资源是独占的,一次只能一个线程获取。如ReentrantLock。
- 共享模式(Share):同时可以被多个线程获取,具体的资源个数可以通过参数指定。如Semaphore/CountDownLatch。
一般情况下,子类只需根据需求来实现某一种模式,当然也有同时实现两种模式的同步类,如ReadWriteLock。
共享式同步状态获取与释放
共享式同步状态获取
在使用共享式获取锁,允许在同一时刻有多个线程同时获取到同步状态。例如文件的读操作被允许有多个。
我们可以通过同步器提供的模板方法中的acquireShared(int arg)来共享式获取同步状态。该方法源码如下所示。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) //tryAcquireShared交由其子类编写
doAcquireShared(arg);
}
它是通过tryAcquireShared方法来尝试获取同步状态,当其返回值大于等于0时,表示能获取到同步状态。这个也是共享式获取的自旋过程中成功获取同步状态并退出自旋的条件。
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
/*
自旋过程中。若当前节点的前驱节点为头节点时,尝试获取同步状态,
若返回值大于等于0,则表示成功获取并从自旋过程中退出。
*/
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null;
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
共享式获取的释放
共享式获取通过调用releaseShared(int arg)方法来释放释放同步状态,该方法在释放后,将唤醒后续处于等待状态的节点。由于是多个线程释放同步状态的操作,因此该方法通过tryReleaseShared(int arg) 方法来确保同步状态线程安全的释放,一般是通过循环和CAS来保证。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
自定义同步组件
**需求:**设计一个同步工具锁TwinsLock,在同一时刻,该工具只允许最多有2个线程同时访问,超过2个线程的访问将被阻塞。
在创建一个锁时,我们需要继承Lock接口,该接口提供了一些方法需要我们实现:

(1)由于访问模式是同一个时刻支持多个线程访问,这显然是共享式访问,因此我们需要使用同步器提供的acquireShared(int args)方法等和Shared相关的方法,即我们需要重写这两个方法。
(2)其次,同步资源数为2,即status为2:当一个线程进行获取,status减1;线程释放,则status加1。当status为0时,尝试获取同步状态的线程只能被阻塞。在同步状态变更时,需要使用compareAndSet(int expect,int update)方法做原子性保障。
(3)一般来说,自定义同步器会被定义为自定义同步组件TwinsLock的内部类。
(1)自定义锁和同步器TwinsLock :
public class TwinsLock implements Lock {
private static final class Sync extends AbstractQueuedSynchronizer {
public Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count必须大于0");
}
setState(2); //初始状态为2
}
/*
当一个线程获取资源时,此时的状态curState减1,
同步状态的变更需要CAS来确保原子性。
*/
@Override
protected int tryAcquireShared(int reduceCount) {
for(;;) {
int curState = getState();
int newState = curState - reduceCount;
if(newState < 0 || compareAndSetState(curState, newState)) {
return newState;
}
}
}
/*
当一个线程释放资源时,此时的状态curState加1,
同步状态的变更需要CAS来确保原子性。
*/
@Override
protected boolean tryReleaseShared(int reduceCount) {
for(;;) {
int curState = getState();
int newState = curState + reduceCount;
if(compareAndSetState(curState, newState)) {
return true;
}
}
}
}
private final Sync sync = new Sync(2); //自定义的组件Sync
@Override
public void lock() {
//通过自定义组件中重写的acquireShared方法来变更state
sync.acquireShared(1);
}
@Override
public void unlock() {
sync.releaseShared(1);
}
//Lock接口的剩余方法为默认,此处省略。
}
(2)任务线程Task
public class Task extends Thread {
final static Lock twinsLock = new TwinsLock();
@Override
public void run() {
while (true) {
twinsLock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁");
try {
Thread.sleep(1);
System.out.println(Thread.currentThread().getName() + "执行任务中");
Thread.sleep(1);
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println(Thread.currentThread().getName() + "释放锁");
twinsLock.unlock();
}
}
}
}
(3)测试一下,会发现最多有两个线程持有锁。
public class Test {
public static void main(String[] args) throws InterruptedException{
new Task().start();
new Task().start();
new Task().start();
}
}
JDK中的锁接口和类
接口Condition/Lock
JUC包下的locks包里有三个接口:Condition、Lock、ReadWriteLock。其中Lock接口有一个获得Condition对象的方法
Condition newCondition();
Condition接口用来替代传统的Object的wait()、notify()来实现线程间的协作:await/signal方法。
void await()
当前线程进入等待状态,直到其他线程调用相同Condition对象的signal/signalAll方法 或者其他线程调用interrupt方法中断当前线程;
void awaitUninterruptibly()
当前线程进入等待状态直到被通知,在此过程中对中断信号不敏感,不支持中断当前线程
long awaitNanos(long nanosTimeout)
当前线程进入等待状态,直到被通知、中断或者超时。如果返回值小于等于0,可以认定为超时了
boolean awaitUntil(Date deadline)
当前线程进入等待状态,直到被通知、中断或者超时。如果没到指定时间就被通知,则返回true,否则返回false
void signal()
唤醒一个等待在Condition上的线程,被唤醒的线程在方法返回前必须持有与Condition对象关联的锁
void signalAll()
唤醒所有等待在Condition上的线程,能够从await()等方法返回的线程必须先获得与Condition对象关联的锁
Object监视器与Condition的比较
(1)等待队列个数:Object监视器有一个,而Condition有多个。
(2)进入等待状态后遇到中断:Obejct的wait方法在遇到中断interrupt后,首先将线程从等待队列移到阻塞队列,然后等待锁的释放,释放后抛出InterruptedException;而Condition的awaitUninterruptibly方法支持等待状态中不中断。
(3)等待的时间设置:Condition的awaitNanos方法支持当前线程释放锁后等待直至某一时间,而Object不支持。
ReentrantLock
大致结构
ReentrantLock是Lock接口的默认实现,它是一个可重入,独占锁。该类的大致结构如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
//具体实现
}
protected final boolean tryRelease(int releases) {
//具体实现
}
}
static final class NonfairSync extends Sync {
//省略了其属性和方法的实现
}
static final class FairSync extends Sync {
//省略了其属性和方法的实现
protected final boolean tryAcquire(int acquires) {
//省略了方法的实现
}
}
}
类的内部有一个继承AQS的抽象类Sync,它是一个自定义同步器。此外还有两个继承自Sync的非抽象类NonfairSync和FairSync,它们分别是”非公平同步器“和”公平同步器“,这意味ReentrantLock可以支持”公平锁“和”非公平锁“。
使用示例
// 1.初始化选择公平锁、非公平锁
ReentrantLock lock = new ReentrantLock(true);
// 2.可用于代码块
lock.lock();
try {
try {
// 3.支持多种加锁方式,比较灵活; 具有可重入特性
if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
} finally {
// 4.手动释放锁
lock.unlock()
}
} finally {
lock.unlock();
}
非公平锁获取同步状态
ReentrantLock的构造方法里可以传入boolean类型的参数,以指定是否为一个公平锁,默认情况为非公平;以默认的非公平锁为例,来看一下ReentrantLock的内部静态类NonfairSync。
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); //点进acquire方法跳至AQS
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
acquire方法
再看看AQS的获取同步状态方法acquire
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizer implements java.io.Serializable {
public final void acquire(int arg) {
//尝试获取同步状态,如果失败则addWaiter将构造节点加入到同步队列尾部。然后acquireQueued使节点以死循环获取同步状态
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//将node添加到队列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 使用CAS尝试将节点插入尾部,若成功就返回添加的节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//若等待队列为空或者上面的CAS失败了,再自旋CAS插入
enq(node);
return node;
}
/*
通过死循环保证节点正确添加,只有通过CAS将节点设置为尾节点之后,当前线程才能从该
方法返回。
*/
private Node enq(final Node node) {
for (;;) { //死循环保证节点肯定被添加
Node t = tail;
if (t == null) { //如果尾节点为空,则CAS设置一个节点为头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t; //CAS将node节点设置为尾节点
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
an}
}
//tail变量在AQS对象上的内存偏移量,通过它直接在内存中修改tail变量
private static final long tailOffset;
static {
try {
//...
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
} catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
}
acquireQueued方法
来看一下加入到队列里的节点获取同步状态的方法acquireQueued
//首先要说明的是:头节点head的下一个节点是当前获取到资源的节点或null。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { //自旋:要么获取同步状态,要么被阻塞
final Node p = node.predecessor();
//如果node的前驱节点p是为头节点head,则node尝试获取资源
if (p == head && tryAcquire(arg)) {
//node节点拿到资源后,将node设置为head。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/*
如果p不是头节点或者p为头节点但当前没有获取到锁(可能是非公平锁被抢了),
此时判断当前node是否要被阻塞(条件是:前驱节点的waitStatus为-1)
*/
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); //将节点状态标记为CANCELLED。后面解释
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
// 靠前驱节点判断当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取node前驱结点的节点状态
int ws = pred.waitStatus;
// 说明头结点处于唤醒状态
if (ws == Node.SIGNAL)
return true;
// waitStatus>0是取消状态
if (ws > 0) {
do {
// 循环向前查找取消节点,把取消节点从队列中剔除
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置前驱节点等待状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
总结一下在队列里的节点尝试获取同步状态的流程:
为了防止因死循环导致CPU资源浪费,我们在shouldParkAfterFailedAcquire方法里通过判断前置节点的状态来决定是否要将当前线程挂起:
cancelAcquire方法
在前面的acquireQueued方法里,它将Node的状态标记为CANCELLED。
private void cancelAcquire(Node node) {
if (node == null)
return;
// 设置该节点不关联任何线程,也就是虚节点
node.thread = null;
Node pred = node.prev;
// 通过前驱节点,跳过取消状态的node
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 此时pred是非取消状态节点,node节点是取消状态节点。pred->node
Node predNext = pred.next;
// 把当前node的状态设置为CANCELLED
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,尝试将从后往前的第一个非取消状态的节点(pred)设置为尾节点
// 如果更新成功,将pred的后续节点设置为null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else { //更新失败
int ws;
//1.非取消状态节点pred不是头节点,且pred是被唤醒状态,2.尝试把pred设置为唤醒节点。
//如果1和2中有一个为true,再判断当前节点的线程是否为null
if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 如果当前节点是head的后继节点,或者上述条件不满足,那就唤醒当前节点的后继节点
unparkSuccessor(node);
}
node.next = node;
}
}
非公平锁释放同步状态
ReentrantLock在解锁的时候,并不区分公平锁和非公平锁
public void unlock() {
sync.release(1);
}
然后调用了AQS类的release方法
// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final boolean release(int arg) {
//返回true表示已没有线程持有锁,
if (tryRelease(arg)) {
Node h = head;
// 头结点不为空并且头结点的waitStatus不是初始化节点,解除线程挂起状态
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/*
在初始情况下,head为空,当第一个节点入队后,head会被初始化为初始化为一个虚拟节点。
*/
tryRelease方法返回 在释放releases个同步状态后,是否还有线程持有状态量。
// java.util.concurrent.locks.ReentrantLock.Sync
// 方法返回当前锁是不是没有被线程持有
protected final boolean tryRelease(int releases) {
// 释放一个状态后的状态量
int c = getState() - releases;
// 当前线程不是持有锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果持有线程全部释放,将当前独占锁所有线程设置为null,并更新state
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
公平锁获取同步状态
在前面提到的nonfairTryAcquire(int acquires)方法中,对于非公平锁而言,只要CAS设置同步状态成功,则表示当前线程获取了锁。但公平锁不同。如下所示,相比于nonfairTryAcquire方法,tryAcquire方法的判断条件多了hasQueuedPredecessors()方法,即多出一个对同步队列中当前节点是否有前驱节点的判断:若该判断方法返回true,表示存在一个线程比当前线程更早的请求锁,因此需要等待前驱线程获取并释放锁后,才能继续获取锁。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*
首先判断当前节点所在的等待队列中是否存在前驱节点,若不存在再尝试CAS更新同步状态。
*/
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;
}
读写锁ReentrantReadWriteLock
ReentrantReadWriteLock是ReadWriteLock接口的JDK默认实现,它与ReentrantLock的功能类似,但它还支持“读写锁”,不过当进行写操作时,是不能进行写操作和读操作的。
该类内部的大致结构如下,同样也是维护了两个同步器,且维护了两个Lock的实现类ReadLock和WriteLock。
// 内部结构
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// 具体实现
}
static final class NonfairSync extends Sync {
// 具体实现
}
static final class FairSync extends Sync {
// 具体实现
}
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// 具体实现
}
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// 具体实现
}
// 构造方法,初始化两个锁
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
// 获取读锁和写锁的方法
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
写锁的获取与释放
写锁是一个支持重进入的排他锁,若当前线程已经获取写锁,则增加写状态;若当前线程在获取写锁时,读锁已被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。
写锁获取同步状态的方法如下,该方法除了判断当前线程是否已获取写锁之外,还增加了读锁是否存在的判断:若读锁存在,则写锁不能获取。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); //写锁的获取次数
if (c != 0) {
// 存在读锁或者当前获取线程不是已经获取写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
return false;
}
setExclusiveOwnerThread(current);
return true;
}
写锁的释放
写锁的释放与与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0 时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时之前写线程的修改对 后续读写线程可见。
读锁的获取与释放
读锁的获取
读锁是一个支持重进入的共享锁,它能被多个线程同时获取,在没有其他写线程访问时,读锁总会被成功获取,增加读状态。
tryAcquireShared方法的部分代码如下。如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全, 依靠CAS保证)增加读状态,成功获取读锁。
protected final int tryAcquireShared(int unused) {
for (;;) {
int c = getState();
int nextc = c + (1 << 16);
if (nextc < c)
throw new Error("Maximum lock count exceeded");
if (exclusiveCount(c) != 0 && owner != Thread.currentThread())
return -1;
if (compareAndSetState(c, nextc))
return 1;
}
读锁释放
读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态,减少的值是(1<<16)。
锁降级
锁降级指的是写锁降级成为读锁,即当前拥有的写锁,再次获取到读锁,随后释放先前拥有的写锁的一个过程。
下面是一个锁降级的示例代码。当数据发生变更后,update变量被设置为false。此时所有访问processData()方法的线程都能得知,但只有一个线程能获取写锁,其他线程会被阻塞在 读锁与写锁的lock()方法上,获取到写锁的线程完成数据准备后,再获取读锁,随后释放写锁完成锁降级。
public void processData() {
readLock.lock();
if (!update) {
// 必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}
readLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}
try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}
锁降级中读锁的获取是必要的。如果当前线程不获取读锁,而是直接释放写锁,假设此时另一个线程T获取了写锁并修改了数据,则当前线程无法察觉线程T对数据的更新。因此只要当前线程获取了读锁,那么线程T就会被阻塞,不会修改数据,知道当前线程释放了读锁,线程T才能获取写锁并更新数据。
RentrantReadWriteLock不支持锁升级(持有读锁->获取写锁->释放读锁)。如果读锁被多个线程获取,其中任意线程成功获取了写锁并更新数据,则此更新对其他获取到读锁的线程是不可见的。
使用示例
public class Test {
class MyTask {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void readTask() {
try {
try {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() +" 获得读锁");
Thread.sleep(3000);
}finally {
System.out.println(Thread.currentThread().getName() +" 释放读锁");
lock.readLock().unlock(); // 写锁为: lock.writeLock().unlock()
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public void writeTask() {
try {
try {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() +" 获得写锁");
Thread.sleep(3000);
}finally {
System.out.println(Thread.currentThread().getName() +" 释放写锁");
lock.writeLock().unlock(); // 写锁为: lock.writeLock().unlock()
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class WriteThread extends Thread {
private MyTask task;
public WriteThread(MyTask task, String name) {
this.task = task;
setName(name);
}
@Override
public void run() {
task.writeTask();
}
}
class ReadThread extends Thread {
private MyTask task = new MyTask();
public ReadThread(MyTask task, String name) {
this.task = task;
setName(name);
}
@Override
public void run() {
task.readTask();
}
}
public static void main(String[] args) {
Test test = new Test();
MyTask task = test.new MyTask();
test.new WriteThread(task, "写锁").start();
test.new ReadThread(task, "读锁A").start();
}
}