10-多线程笔记-2-锁-3-Lock-4-工具类
StampedLock
StampedLock是并发包里面jdk8版本新增的一个锁,该锁提供了三种模式的读写控制,三种模式分别如下:
写锁writeLock,是个排它锁或者叫独占锁,同时只有一个线程可以获取该锁,当一个线程获取该锁后,其它请求的线程必须等待,当目前没有线程持有读锁或者写锁的时候才可以获取到该锁,请求该锁成功后会返回一个stamp票据变量用来表示该锁的版本,当释放该锁时候需要unlockWrite并传递参数stamp。
悲观读锁readLock,是个共享锁,在没有线程获取独占写锁的情况下,同时多个线程可以获取该锁,如果已经有线程持有写锁,其他线程请求获取该读锁会被阻塞。这里讲的悲观其实是参考数据库中的乐观悲观锁的,这里说的悲观是说在具体操作数据前悲观的认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据加锁,这是在读少写多的情况下的一种考虑,请求该锁成功后会返回一个stamp票据变量用来表示该锁的版本,当释放该锁时候需要unlockRead并传递参数stamp。
乐观读锁tryOptimisticRead,是相对于悲观锁来说的,在操作数据前并没有通过CAS设置锁的状态,如果当前没有线程持有写锁,则简单的返回一个非0的stamp版本信息,获取该stamp后在具体操作数据前还需要调用validate验证下该stamp是否已经不可用,也就是看当调用tryOptimisticRead返回stamp后到到当前时间间是否有其他线程持有了写锁,如果是那么validate会返回0,否者就可以使用该stamp版本的锁对数据进行操作。由于tryOptimisticRead并没有使用CAS设置锁状态所以不需要显示的释放该锁。该锁的一个特点是适用于读多写少的场景,因为获取读锁只是使用与或操作进行检验,不涉及CAS操作,所以效率会高很多,但是同时由于没有使用真正的锁,在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性还是得到保障的。
LockSupport
LockSupport 工具类是 JUC 的基础组件,主要作用是用来阻塞和唤醒线程,底层依赖于 Unsafe 类实现。
LockSupport 主要定义类 2 类方法:park 和 unpark,其中 park 方法用于阻塞当前线程,而 unpark(Thread) 方法用于唤醒处于阻塞状态的指定线程。
CountDownLatch
CountDownLatch是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作集。
CountDownLatch使用给定的计数值(count)初始化。await方法会阻塞直到当前的计数值(count)由于countDown方法的调用达到0,在这之后(即,count为0之后)所有等待的线程都会被释放,并且随后对await方法的调用都会立即返回。这是一个一次性现象 ———— count不会被重置。如果你需要一个重置count的版本,那么请考虑使用CyclicBarrier。
java.util.concurrent.CountDownLatch.Sync
CountDownLatch依赖AQS实现线程的阻塞与唤醒,其内部类Sync是AQL类的子类,实现了共享锁的获取与释放方法;
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 此处用于指定等待线程的个数
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
// 获取共享锁逻辑,是CountDownLatch的await方法的核心代码
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 将资源数减1
// 只有将资源数减为0时,才会释放所有阻塞线程,其他情况都不做特殊处理
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
java.util.concurrent.CountDownLatch#await()/ await(long timeout, TimeUnit unit)
await() 阻塞调用线程,除非达到以下两种情况:
- 通过调用countDown函数,将计数器值减少到0;
- 其他线程中断了此线程;
await(long timeout, TimeUnit unit)阻塞调用线程,满足以下三种条件后,会被唤醒:
- 通过调用countDown函数,将计数器值减少到0;
- 等待时间超过设定值,超时后自动返回,如果计数器为0返回true,否则返回false;
- 其他线程中断了此线程;
JDK11
-
await()
// 阻塞调用线程,除非达到以下两种情况 // 1. 通过调用countDown函数,将计数器值减少到0; // 2. 其他线程中断了此线程 public void await() throws InterruptedException { // 底层依赖的是AQS的可中断共享锁 sync.acquireSharedInterruptibly(1); } // AQS中获取可中断共享锁方法 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { // 如果线程中断,抛出异常 if (Thread.interrupted()) throw new InterruptedException(); // 如果成功获取共享锁(计数器值已经被减少到0)直接返回,否则加入到阻塞队列 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } // 获取共享锁逻辑,是CountDownLatch的await方法的核心代码 protected int tryAcquireShared(int acquires) { // 如果计数器值为0,调用wait不会阻塞线程;否则将当前线程加入到阻塞队列 return (getState() == 0) ? 1 : -1; }
-
boolean await(long timeout, TimeUnit unit)
// 阻塞调用线程,满足以下三种条件后,会被唤醒 // 1. 通过调用countDown函数,将计数器值减少到0; // 2. 等待时间超过设定值,超时后自动返回,如果计数器为0返回true,否则返回false; // 3. 其他线程中断了此线程 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } // 获取定时可中断共享锁 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { // 线程中断,抛出异常 if (Thread.interrupted()) throw new InterruptedException(); // 如果成功获取共享锁(计数器值已经被减少到0)直接返回true,否则加入到阻塞队列 return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout); } // 获取共享锁逻辑,是CountDownLatch的await方法的核心代码 protected int tryAcquireShared(int acquires) { // 如果计数器值为0,调用wait不会阻塞线程;否则将当前线程加入到阻塞队列 return (getState() == 0) ? 1 : -1; }
java.util.concurrent.CountDownLatch#countDown
减少计数器的值(调用一次该方法,计数器值减1),如果计数器值减为0,唤醒所有阻塞线程;
- 如果计数器的值大于0,计数器的值减1,如果新值等于零,唤醒所有阻塞线程;
- 如果计数器的值等于0,什么都不做;
IN JDK11
/**
* 减少计数器的值(调用一次该方法,计数器值减1),如果计数器值减为0,唤醒所有阻塞线程
* 1. 如果计数器的值大于0,计数器的值减1,如果新值等于零,唤醒所有阻塞线程
* 2. 如果计数器的值等于0,什么都不做
*/
public void countDown() {
sync.releaseShared(1);
}
// AQS类中的释放共享锁方法
public final boolean releaseShared(int arg) {
// 如果计数器值减1后,新值为0
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
// 如果计数器值为0,或者新值不为0
return false;
}
// java.util.concurrent.CountDownLatch.Sync#tryReleaseShared
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 如果计数器为0,什么都不做
if (c == 0)
return false;
int nextc = c - 1;
// 设置新值
if (compareAndSetState(c, nextc))
// 如果新值为0返回true
return nextc == 0;
}
}
CyclicBarrier
一个同步辅助工具,允许一组线程在到达同一个状态前彼此等待;适用于一组固定数量的线程需要彼此等待的情况;之所以叫循环(cyclic)栅栏,是因为当一组线程释放后,可以重用此工具;
CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。
CyclicBarrier通过ReetratLock保证多线程安全,所有阻塞线程有ReetranntLock中的等待队列(Condition)维护;
如果一个线程被中断,则所有的线程都将被唤醒
CyclicBarrier与CountDownLatch比较
1)CountDownLatch:一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行;CyclicBarrier:N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
2)CountDownLatch:一次性的;CyclicBarrier:可以重复使用。
3)CountDownLatch基于AQS;CyclicBarrier基于锁和Condition。本质上都是依赖于volatile和CAS实现的。
await()
当前线程等待直到其他线程到达屏障状态或超长指定的等待时间;
如果当前线程不是最后一个到达屏障状态的线程,线程会阻塞直到以下几种情况发生:
- 最后一个线程到达屏障状态;
- 超出指定的等待时间;
- 其他线程中断此线程;
- 所有等待线程中,有一个被中断(等待线程中任意一个被中断,都将唤醒其他等待线程)
- 任意其他线程等待超时;
- 任意线程重置屏障;
如果当前线程在调用此方法时被中断或在等待队列时被中断,会抛出InterruptedException异常
如果屏障被重置时(调用reset方法),有等待线程,线程组的其他线程会抛出BrokenBarrierException异常,并且屏障会被置为broken状态;
/**
* 当前线程等待直到其他线程到达屏障状态或超长指定的等待时间
* 如果当前线程不是最后一个到达屏障状态的线程,线程会阻塞直到以下几种情况发生:
* 1. 最后一个线程到达屏障状态;
* 2. 超出指定的等待时间;
* 3. 其他线程中断此线程;
* 4. 所有等待线程中,有一个被中断(等待线程中任意一个被中断,都将唤醒其他等待线程)
* 5. 任意其他线程等待超时;
* 6. 任意线程重置屏障;
* 如果当前线程在调用此方法时被中断或在等待队列时被中断,会抛出InterruptedException异常
* 如果屏障被重置时(调用reset方法)有等待线程,线程组的其他线程会抛出BrokenBarrierException异常,并且屏障会被置为broken状态
*/
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 加入到等待队列
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
final Generation g = generation;
// 如果当前屏障被其他线程置为异常,抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 如果线程被中断,将屏障状态置为broken,释放所有等待线程,抛出异常
if (Thread.interrupted()) {
//将屏障状态置为broken,释放所有等待线程
breakBarrier();
throw new InterruptedException();
}
// 计数器减1
int index = --count;
// 如果当前线程是最后一个到达屏障状态的
if (index == 0) { // tripped
boolean ranAction = false;
try {
// 获取CyclicBarrier创建时指定的执行命令并执行
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 释放所有等待线程,将计数器重置(创建时指定的线程组大小),重置屏障;
nextGeneration();
return 0;
} finally {
// 如果没有成功执行命令,将屏障状态置为broken,释放所有等待线程
if (!ranAction)
//将屏障状态置为broken,释放所有等待线程
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
// 如果没有设置等待超时,直接将当前线程加入到等待队列,线程会阻塞在此处直到被唤醒或被中断
if (!timed)
trip.await();
// 否则,将线程加入到等待队列并指定超时时间,线程会阻塞在此处直到被唤醒或被中断
else if (nanos > 0L)
// 返回剩余等待时间,如果不大于0,等待超时
nanos = trip.awaitNanos(nanos);
// 如果在加入到队列过程中,当前线程被中断
} catch (InterruptedException ie) {
// 如果当前屏障状态不为borken
if (g == generation && ! g.broken) {
//将屏障状态置为broken,释放所有等待线程
breakBarrier();
// 抛出中断异常
throw ie;
// 如果屏障状态为broken,中断当前线程
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
// 如果屏障状态为broken(其他线程修改的),抛出BrokenBarrierException异常
if (g.broken)
throw new BrokenBarrierException();
// 如果屏障被重置了,返回未到达屏障状态的线程数(此线程如果是被最后一个线程唤醒的,会返回记录的数据)
if (g != generation)
return index;
// 如果等待超时0,抛出异常
if (timed && nanos <= 0L) {
//将屏障状态置为broken,释放所有等待线程
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 释放锁
lock.unlock();
}
}
private void breakBarrier() {
// 将屏障状态置为broken(此值初始化时为false)
generation.broken = true;
// 将计数器置为初始值(CyclicBarrier创建时指定的线程组大小)
count = parties;
// 释放等待队列中所有的线程
trip.signalAll();
}
Semaphores
Semaphores(信号量),代表一组许可证,调用acquire方法,会获取一个许可证或阻塞直到获取一个许可证;调用release会归还一个许可证;Semaphores不代表实际资源,只代表可用资源数量;
Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略。当初始值为1时,可以用作互斥锁,并具备不可重入的加锁语义。Semaphore使用AQS的同步状态用保存当前可用许可的数量。