Java 并发工具包
AQS:AbstractQueuedSynchronizer(抽象队列同步器),阻塞式锁和相关同步器工具的框架
特点:
子类主要实现这样一些方法(不重写的话是会默认抛出 UnsupportedOperationException)
获取锁的姿势
// 如果获取锁失败
if (!tryAcquire(arg)) {
// 入队, 可以选择阻塞当前线程 park unpark
}
释放锁的姿势
// 如果释放锁成功
if (tryRelease(arg)) {
// 让阻塞线程恢复运行
}
package com.rainsun.d7_thread_pool;
import lombok.extern.slf4j.Slf4j;
import java.net.MulticastSocket;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Slf4j(topic = "c.d3_TestAqs")
public class d3_TestAqs {
public static void main(String[] args) {
MyLock lock = new MyLock();
new Thread(()->{
lock.lock();
try {
log.debug("locking...");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
log.debug("unlocking...");
lock.unlock();
}
}, "t1").start();
new Thread(()->{
lock.lock();
try {
log.debug("locking...");
} finally {
log.debug("unlocking...");
lock.unlock();
}
}, "t2").start();
}
}
// 自定义锁(不可重入锁)
class MyLock implements Lock {
private MySync sync = new MySync();
class MySync extends AbstractQueuedSynchronizer{
@Override // 独占锁
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0, 1)){
// 加上了锁,设置 owner 为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override // 是否持有独占锁
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
17:19:48 [t1] c.d3_TestAqs - locking...
17:19:49 [t1] c.d3_TestAqs - unlocking...
17:19:49 [t2] c.d3_TestAqs - locking...
17:19:49 [t2] c.d3_TestAqs - unlocking...
ReentrantLock 实现了 Lock 接口,内部有一个 Sync 的类继承自 AbstractQueuedSynchronizer (AQS),内部还有两个锁实现非公平锁和公平锁都继承了 Sync 类
默认创建非公平锁,当没有竞争时:执行的线程为当前线程
final boolean initialTryLock() {
Thread current = Thread.currentThread();
if (compareAndSetState(0, 1)) { // first attempt is unguarded
setExclusiveOwnerThread(current);
return true;
} else if (getExclusiveOwnerThread() == current) {
int c = getState() + 1;
if (c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
} else
return false;
}
第一个竞争出现时:CAS 修改 state 失败,进入FIFO队列等待
Thread-1 执行了
CAS 尝试将 state 由 0 改为 1,结果失败
@ReservedStackAccess
final void lock() {
if (!initialTryLock())
acquire(1);
}
进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
接下来进入 addWaiter 逻辑,将 Thread 封装为Node,构造 Node 队列
当前线程进入 acquireQueued 逻辑
再次有多个线程经历上述过程竞争失败,变成这个样子
Thread-0 释放锁,进入 tryRelease 流程,如果成功
当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程
如果加锁成功(没有竞争),会设置
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
如果不巧又被 Thread-4 占了先
加锁时,state 加1:
final boolean initialTryLock() {
Thread current = Thread.currentThread();
if (compareAndSetState(0, 1)) { // first attempt is unguarded
setExclusiveOwnerThread(current);
return true;
} else if (getExclusiveOwnerThread() == current) {
int c = getState() + 1;
if (c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
} else
return false;
}
getExclusiveOwnerThread() == current
表示当前线程已经获得锁了
这时会 int c = getState() + 1;
state 的值加1
解锁时,state 减 1:
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // state - 1
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0); // 只有 state 等于 0 才会 free,释放锁
if (free)
setExclusiveOwnerThread(null);
setState(c);
return free;
}
不可打断模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
// ...
for (;;) {
Node t;
if ((t = tail) == null) {
// ...
} else {
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed)
LockSupport.park(this);
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else
break;
node.clearStatus();
if ((interrupted |= Thread.interrupted()) && interruptible) // Thread.interrupted()打断了,但是如果interruptible为false 并不会 break 停止,只将 interrupted标记 置为 true 了
break;
}
}
return cancelAcquire(node, interrupted, interruptible);
}
private int cancelAcquire(Node node, boolean interrupted,
boolean interruptible) {
if (node != null) {
node.waiter = null;
node.status = CANCELLED;
if (node.prev != null)
cleanQueue();
}
if (interrupted) {
if (interruptible)
return CANCELLED;
else
Thread.currentThread().interrupt();
}
return 0;
}
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 队列中没有元素才会竞争
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
/**
* Acquires only if thread is first waiter or empty
*/
protected final boolean tryAcquire(int acquires) {
// hasQueuedPredecessors 会检查当前 AQS 队列是否有前驱节点,没有才会竞争
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select … from … lock in share mode
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
package com.rainsun.d8_JUC;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j(topic = "c.d2_ReadWriteLockTest")
public class d2_ReadWriteLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(()->{
dataContainer.read();
},"t1").start();
Thread.sleep(100);
new Thread(()->{
dataContainer.write();
},"t2").start();
}
}
@Slf4j(topic = "c.DataContainer")
class DataContainer{
private Object data;
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rw.readLock();
private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
public Object read(){
log.debug("get read lock ...");
r.lock();
try {
log.debug("read");
Thread.sleep(1000);
return data;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
r.unlock();
log.debug("release read lock...");
}
}
public void write(){
log.debug("get write lock");
w.lock();
try {
log.debug("write");
}finally {
log.debug("release write lock");
w.lock();
}
}
}
11:22:49 [t1] c.DataContainer - get read lock ...
11:22:49 [t1] c.DataContainer - read
11:22:49 [t2] c.DataContainer - get write lock
11:22:50 [t1] c.DataContainer - release read lock...
11:22:50 [t2] c.DataContainer - write
11:22:50 [t2] c.DataContainer - release write lock
读完才能写,读读是并发的
注意事项
升级:读锁里包含写锁,则写锁会一直等待读完,但是读在等待写完
r.lock();
try {
// ...
w.lock();
try {
// ...
} finally{
w.unlock();
}
} finally{
r.unlock();
}
写锁后,在内部加上读锁,这样当写锁释放的时候,就获得了读锁。就能保证自己写的数据不会被其他线程获得写锁而篡改(因为我获取了读锁,与其他线程的写锁互斥,其他线程的写锁就不能改变我刚改的值)
class CachedData {
Object data;
// 是否有效,如果失效,需要重新计算 data
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// 获取写锁前必须释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 降级为读锁, 释放写锁时可以获取读锁,这样能够让其它线程读取缓存,但写不进去
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock();
}
}
// 自己用完数据, 释放读锁
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个
不同的是,state 中的低 16 为用于给写锁状态,高 16 用于给读锁状态计数
(1)t1 w.lock
t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位。
即 state 低 16 位为 1,ownerThread 为当前的 t1 线程
(2)t2. r.lock
t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
acquire(null, arg, true, false, false, 0L);
}
@ReservedStackAccess
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 有写锁,且不是当前线程的写锁,那就不可以加读锁
// 如果有写锁但是当前线程的写锁,那可以加读锁,因为可以锁降级
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// ...
return 1;
}
return fullTryAcquireShared(current);
}
3)这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态
4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁
5)如果没有成功,在 doAcquireShared 内 for (; 循环一次,把前驱节点的 waitStatus 改为 -1,再 for (; 循环一次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() 处 park
(6)t3 r.lock,t4 w.lock
这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子
t1 w.unlock:
这时会走到写锁的 sync.release(1) 流程,调用 sync.tryRelease(1) 成功,变成下面的样子
接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在 doAcquireShared 内 parkAndCheckInterrupt() 处恢复运行
这回再来一次 for (; 执行 tryAcquireShared 成功则让读锁计数加一
这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点
事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireShared 内parkAndCheckInterrupt() 处恢复运行
这回再来一次 for (; 执行 tryAcquireShared 成功则让读锁计数加一
这时 t3 已经恢复运行,接下来 t3 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点
下一个节点不是 shared 了,因此不会继续唤醒 t4 所在节点
t2 r.unlock,t3 r.unlock
t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但由于计数还不为零
t3 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,这回计数为零了,进入 doReleaseShared() 将头节点从 -1 改为 0 并唤醒老二,即
之后 t4 在 acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次 for (; 这次自己是老二,并且没有其他 竞争,tryAcquire(1) 成功,修改头结点,流程结束
该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
Semaphore 用来限制能同时访问共享资源的线程上限。
@Slf4j(topic = "c.d3_SemaphoreTest")
public class d3_SemaphoreTest {
public static void main(String[] args) {
// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10 线程同时执行
for (int i = 0; i < 10; i++) {
new Thread(()->{
// 3. acquire 获得许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
log.debug("running ... ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("end...");
} finally {
// 4. 释放许可
semaphore.release();
}
}).start();
}
}
}
Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后停车场显示空余车位减一
刚开始,permits(state)为 3,这时 5 个线程来获取资源
假设其中 Thread-1,Thread-2,Thread-4 cas 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列 park 阻塞
这时 Thread-4 释放了 permits,接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态
用来进行线程同步协作,等待所有线程完成倒计时。
其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一
@Slf4j(topic = "c.d4_CountDownLatchTest")
public class d4_CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
log.debug("begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
log.debug("end...");
}).start();
new Thread(()->{
log.debug("begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
log.debug("end...");
}).start();
new Thread(()->{
log.debug("begin...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
log.debug("end...");
}).start();
log.debug("waiting...");
countDownLatch.await();
log.debug("waiting end...");
}
}
配合线程池使用:
@Slf4j(topic = "c.d4_CountDownLatchTest")
public class d4_CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(4);
CountDownLatch countDownLatch = new CountDownLatch(3);
service.submit(()->{
log.debug("begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
log.debug("end...{}", countDownLatch.getCount());
});
service.submit(()->{
log.debug("begin...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
log.debug("end...{}", countDownLatch.getCount());
});
service.submit(()->{
log.debug("begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
log.debug("end...{}", countDownLatch.getCount());
});
service.submit(()->{
log.debug("waiting...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("waiting end...");
});
}
}
16:38:58 [pool-1-thread-4] c.d4_CountDownLatchTest - waiting...
16:38:58 [pool-1-thread-2] c.d4_CountDownLatchTest - begin...
16:38:58 [pool-1-thread-1] c.d4_CountDownLatchTest - begin...
16:38:58 [pool-1-thread-3] c.d4_CountDownLatchTest - begin...
16:38:59 [pool-1-thread-1] c.d4_CountDownLatchTest - end...2
16:38:59 [pool-1-thread-2] c.d4_CountDownLatchTest - end...1
16:39:00 [pool-1-thread-3] c.d4_CountDownLatchTest - end...0
16:39:00 [pool-1-thread-4] c.d4_CountDownLatchTest - waiting end...
等待其他线程完成的 demo:
public class d5_CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(10);
String[] allstate = new String[10];
for(int i = 0; i < 10; ++ i){
int finalI = i;
new Thread(()->{
for(int load = 0; load <= 100; ++ load){
allstate[finalI] = load + "%";
System.out.print("\r" + Arrays.toString(allstate));
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("\nGame begin !");
service.shutdown();
}
}
循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,每等待一个线程计数个数就加1
当等待的线程数满足『计数个数』时,即最后一个线程到达同步点的屏障时,屏障解除,线程继续执行
CyclicBarrier 与 CountDownLatch 的主要区别在于 CyclicBarrier 是可以重用的
CyclicBarrier 可以被比喻为『人满发车』,没坐满,前面已上车的线程就需要继续等待
@Slf4j(topic = "c.d6_CyclicBarrierTest")
public class d6_CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
CyclicBarrier barrier = new CyclicBarrier(2, ()->{
log.debug("task1 task2 finish...");
});
for (int i = 0; i < 3; i++) {
service.submit(()->{
log.debug("task1 begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
// 初始计数-1
barrier.await();
log.debug("task1 end...");
} catch (Exception e) {
e.printStackTrace();
}
});
service.submit(()->{
log.debug("task2 begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
// 初始计数-1
barrier.await();
log.debug("task2 end...");
} catch (Exception e) {
e.printStackTrace();
}
});
}
service.shutdown();
}
}