并发编程七、J.U.C并发工具及原理分析 02
有了之前JUC中Lock、AQS同步队列、Condition线程通信、Condition等待队列的基础,我们可以继续研究JUC包中如BlockingQueue、CountDownLatch、Semaphore、CyclicBarrier等并发工具,这些并发工具其实都是在重入锁、Condition基础上设计出来的。
一、BlockingQueue 阻塞队列
阻塞队列和分布式消息通信的MQ有些相似,可以应用于程序内 异步、解耦、削峰 的场景,来提升运行效率。
1. J.U.C提供的阻塞队列
在 Java 8 中,提供了7个阻塞队列
队列名称 | 描述 |
---|---|
ArrayBlockingQueue | 数组实现的有界阻塞队列, 此队列按照先进先出(FIFO)的原则对元素进行排序。 |
LinkedBlockingQueue | 链表实现的有界阻塞队列, 此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列, 默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。 |
DelayQueue | 优先级队列实现的无界阻塞队列 |
SynchronousQueue | 不存储元素的阻塞队列, 每一个put操作必须等待一个take操作,否则不能继续添加元素 |
LinkedTransferQueue | 链表实现的无界阻塞队列 |
LinkedBlockingDeque | 链表实现的双向阻塞队列 |
阻塞队列的操作方法
在阻塞队列中,提供了四种处理方式
操作类型 | 操作名称 | 描述 |
---|---|---|
插入操作 | add(e) | 添加元素到队列中,如果队列满了,继续插入元素会报错, IllegalStateException |
插入操作 | offer(e) | 添加元素到队列,同时会返回元素是否插入成功的状态,如果成功则返回 true |
插入操作 | put(e) | 当阻塞队列满了以后;生产者继续通过put添加元素,队列会一直阻塞生产者线程,直到队列可用 |
插入操作 | offer(e,time,unit) | 当阻塞队列满了以后继续添加元素生产者线程会被阻塞指定时间,如果超时,则线程直接退出 |
移除操作 | remove() | 当队列为空时,调用 remove 会返回 false如果元素移除成功,则返回 true |
移除操作 | poll() | 当队列中存在元素,则从队列中取出一个元素,如果队列为空,则直接返回 null |
移除操作 | take() | 基于阻塞的方式获取队列中的元素,如果队列为空,则 take 方法会一直阻塞,直到队列中有新的数据可以消费 |
移除操作 | poll(time,unit) | 带超时机制的获取数据,如果队列为空则会等待指定的时间再去获取元素返回 |
2. 使用案例 BlockingQueue、take、put
阻塞队列比较多的仍然是对于生产者消费者场景的应用,但是由于分布式架构的普及,使得大家更多的关注在分布式消息队列上。 所以其实如果把阻塞队列比作成分布式消息队列的话,那么所谓的生产者和消费者其实就是基于阻塞队列的解耦。另外,阻塞队列是一个 fifo 的队列,所以对于希望在线程级别需要实现对目标服务的顺序访问的场景中,也可以使用。
以注册用户的模拟场景为例,注册用户内部分为了两个步骤:1、保存用户;2、赠送积分;加入这两个步骤每个执行都需要2秒,样例代码如下:
User
public class User {
private String name;
public User(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
UserService
public class UserService {
public Boolean register(String name) {
long start = new Date().getTime();
User user = new User(name);
saveUser(user);
sendPoints(user);
long end = new Date().getTime();
System.out.println("register cost " + (end - start) + "ms" );
return true;
}
public void saveUser(User user) {
// do something to save user
System.out.println("保存用户 " + user + " ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sendPoints(User user) {
// do something to save points
System.out.println("添加积分 " + user + " ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new UserService().register("shen");
}
}
... 运行结果
保存用户 User{name='shen'}
添加积分 User{name='shen'}
register cost 4005ms
因为这个场景是简单的串行执行,用户注册的两个步骤分别为2s,则总的流程为4s左右。
这个场景明显效率是很低的,我们此时可以借助阻塞队列让这两个步骤异步执行,来实现效率的优化。
UserService 改造
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class UserService {
private static volatile Boolean isRunning = true; // 消费者线程的控制,volatile保证可见性
ArrayBlockingQueue<User> userQueue = new ArrayBlockingQueue<>(100);
public UserService() {
init(); // 对象初始化时,启动一个线程异步从阻塞队列中取数据
}
private void init() {
new Thread(()-> {
while (isRunning) {
try {
User user = userQueue.take();
sendPoints(user);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "SavePoints Thread").start();
}
public Boolean register(String name) {
long start = new Date().getTime();
User user = new User(name);
saveUser(user);
// sendPoints(user); // 放入阻塞队列中,异步执行,另一线程异步从阻塞队列中读取数据来执行
try {
userQueue.put(user);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = new Date().getTime();
System.out.println("register cost " + (end - start) + "ms" );
return true;
}
public void saveUser(User user) {
// do something to save user
System.out.println("保存用户 " + user + " ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sendPoints(User user) {
// do something to save points
System.out.println("添加积分 " + user + " ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new UserService().register("shen");
}
}
... 运行结果
保存用户 User{name='shen'}
register cost 2002ms
添加积分 User{name='shen'}
改造后的逻辑,我们使用了一个默认长度为100的数组类型的有界阻塞队列,在程序启动时创建一个异步线程来从队列中取数据异步执行添加积分的操作,在注册用户时保存完用户后直接将用户放入阻塞队列中去异步添加积分。
3. 源码分析
从上面的用户注册例子,以ArrayBlockingQueue为例来分析阻塞队列的源码实现,主要可以分为几个步骤:
a. 创建一个长度为100的阻塞队列ArrayBlockingQueue
b. 异步线程`SavePoints Thread`从队列中取数据 take,此时队列中无数据、阻塞
c. 主线程将用户放入阻塞队列中,唤醒异步线程SavePoints
d. SavePoints线程被唤醒,消费数据
a. 创建一个长度为100的阻塞队列ArrayBlockingQueue
java.util.concurrent.ArrayBlockingQueue#
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty; // 控制条件 notEmpty : can take; 队列非空, 可以取数据
/** Condition for waiting puts */
private final Condition notFull; // 控制条件 notFull : can put; 队列未满, 可以存数据
public ArrayBlockingQueue(int capacity) {
this(capacity, false); // 默认fair=fasle
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity]; // 创建一个长度为100的数组
lock = new ReentrantLock(fair); // fair=false,创建非公平锁
notEmpty = lock.newCondition(); // 创建控制条件notEmpty, notEmpty : can take; 非空条件:当队列不为空时,可以取数据;当队列为空,线程阻塞
notFull = lock.newCondition(); // 控制条件 notFull : can put; 用来控制当队列已满时,阻塞存入数据的线程; 当队列未满时,可以继续往队列中存入数据
}
b. 异步线程SavePoints Thread
从队列中取数据 take,此时队列中无数据、阻塞
UserService
private void init() {
new Thread(()-> {
while (isRunning) {
try {
User user = userQueue.take(); // B01. 从队列中取数据,此时主线程并没有将user添加入队列,应该阻塞
sendPoints(user);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "SavePoints Thread").start();
}
java.util.concurrent.ArrayBlockingQueue#
/** Number of elements in the queue */
int count; // 队列长度
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // B02-B06. 加锁,为了保证取数据的线程安全; lockInterruptibly : 在线程中断时会抛出异常; 此时锁未被占有,加锁成功
try {
while (count == 0) // count 代表队列中元素个数,此时队列为空, count = 0
notEmpty.await(); // B07. Condition await 阻塞, 因为队列为空, notEmpty不成立,await阻塞当前取数据线程
return dequeue();
} finally {
lock.unlock(); // 释放锁
}
}
java.util.concurrent.locks.ReentrantLock#lockInterruptibly
// B02. 加锁
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1); // B03.抢占1次锁
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireInterruptibly
// B03.抢占1次锁
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) // 线程未被中断过 fasle
throw new InterruptedException();
if (!tryAcquire(arg)) // B04. 尝试抢占1次锁
doAcquireInterruptibly(arg);
}
java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
// B04. 尝试抢占1次锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); // B05. 非公平式的抢占锁
}
java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
// B05. 非公平式的抢占锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 取得当前线程
int c = getState(); // 取得加锁的共享变量标识, 此时没有线程抢占锁 state为0
if (c == 0) { // true
if (compareAndSetState(0, acquires)) { // CAS 将state由0改为1; state 0 -> 1
setExclusiveOwnerThread(current); // 标记当前线程为持有锁的线程 exclusiveOwnerThread
return true; // B06. 抢占锁成功,返回true
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()
// B07 Condition await 阻塞线程 主要四个步骤: a.当前线程加入Condition等待队列, B.释放锁, C.唤醒AQS队列中等着抢占该锁的线程,此时没有, D.阻塞当前线程
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // B08-B09. 当前线程加入Condition等待队列
int savedState = fullyRelease(node); // B10-B15. 一次性释放全部重入锁,返回重入次数 1
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // B16-B17. 判断当前节点是否在AQS同步队列中,明显不在
LockSupport.park(this); // B18. park阻塞当前线程,此时将从队列中取数据的线程阻塞
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter
// B08. 当前线程加入Condition等待队列
private Node addConditionWaiter() {
Node t = lastWaiter; // lastWaiter,为null
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 将当前节点封装为 CONDITION类型节点
if (t == null)
firstWaiter = node; // lastWaiter是null, 表示 Condition等待队列为空,当前节点则为第一个节点
else
t.nextWaiter = node;
lastWaiter = node; // 新添加的节点,肯定为最后一个节点
return node; //B09. 返回当前线程对应节点
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#fullyRelease
// B10. 一次性释放全部重入锁
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 取得当前加锁state, 此时为1
if (release(savedState)) { // B11-B14. 释放锁、唤醒下一个线程
failed = false;
return savedState; // B15. 返回1
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
// B11. 释放锁、唤醒下一个线程
public final boolean release(int arg) {
if (tryRelease(arg)) { // B12-B13. 试图释放锁, 为true
Node h = head; // AQS队列中的Head节点为null
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true; // B14. 释放锁成功,返回true
}
return false;
}
java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
// B12. 试图释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 1 - 1
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 此处判断为true
free = true;
setExclusiveOwnerThread(null); // 占有锁线程置为null
}
setState(c); // state重置为0
return free; // B13. 返回true
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#isOnSyncQueue
// B16. 判断当前节点是否在AQS同步队列中
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null) // 当前节点node是CONDITION类型,此处可以匹配
return false; // B17. 直接返回false,不在AQS同步队列中
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
c. 主线程将用户放入阻塞队列中,唤醒异步线程SavePoints
// 主线程put:将用户放入阻塞队列中,唤醒B步骤阻塞的线程,SavePoints线程从队列中取数据
public Boolean register(String name) {
long start = new Date().getTime();
User user = new User(name);
saveUser(user);
// sendPoints(user);
try {
userQueue.put(user);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = new Date().getTime();
System.out.println("register cost " + (end - start) + "ms" );
return true;
}
java.util.concurrent.ArrayBlockingQueue#put
// C01. 往阻塞队列中放入数据
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // C02. 主线程获取锁
try {
while (count == items.length) // 判断队列是否已满,已满的话阻塞put的线程, 此时未满不阻塞
notFull.await();
enqueue(e); // C03-C09. 放入阻塞队列
} finally {
lock.unlock(); // C10. 主线程释放锁
}
}
java.util.concurrent.ArrayBlockingQueue#enqueue
** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
// C03. 放入阻塞队列
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items; // 此时的items为 new ArrayBlockingQueue(16)时初始化的 Object[]数组
items[putIndex] = x; // 放入第一个元素, 此时放入的下标为0, items[0] = x
if (++putIndex == items.length) // 两个步骤:1. 更改下次放入元素的下标位置,默认+1; 2. 如果下标位置和数组长度相等,肯定不能再+1导致数组越界,而是由0重新开始
putIndex = 0; // 当下一下标位置和数组长度相等, 下一个放入元素的下标则为从0开始【因为本质是一个FIFO的数组队列,存取数据都是从0~16来存储,存满之后等另一个线程取出数据后仍从0开始存入队列,循环使用】
count++; // 数组内元素数量标识 +1
notEmpty.signal(); // C04-C09. 此时队列非空,notEmpty条件满足,从CONDITION等待队列中唤醒
}
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#signal
// C04. 唤醒等带队列中的firstWaiter
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter; // firstWaiter即为B步骤await添加的CONDITION节点
if (first != null)
doSignal(first); // C05-C09. 唤醒第一个等待节点 first
}
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#doSignal
// C05. 唤醒第一个等待节点 first
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && //C06-C09. 将Condition节点从等待队列转移至AQS队列成功, transferForSignal为true, !transferForSignal为false,退出循环
(first = firstWaiter) != null);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#transferForSignal
//C06. 将Condition节点从等待队列转移至AQS队列
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node); // C07-C08. 重新入队
int ws = p.waitStatus; // p.waitStatus = 0
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true; // C09. 返回true
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
// C07. 重新入队
private Node enq(final Node node) {
for (;;) { // 自旋,本次重新入队会有两次自旋,第一次 head,tail为空,赋初始值; 第二次 将当前节点放入AQS队列尾部,tail; 返回 head
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t; // C08. 返回head节点
}
}
}
}
java.util.concurrent.locks.ReentrantLock#unlock
// C10. 主线程释放锁
public void unlock() {
sync.release(1); //C11.释放1次重入锁
}
//C11.释放1次重入锁
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放锁,成功
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // C12. 唤醒下一节点线程
return true;
}
return false;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
//C12. 唤醒下一节点线程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;// 取得head.next, 即为C04 signal唤醒的线程SendPoints
if (s == null || s.waitStatus > 0) { // false
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // C13. unpark唤醒
}
... 至此,put逻辑结束并唤醒了之前阻塞的take数据的线程
d. SavePoints线程被唤醒,消费数据
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()
//消费线程被唤醒
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //D03. 此时node在AQS同步队列中,isOnSyncQueue为true, !isOnSyncQueue为false
LockSupport.park(this); // D01. 从park这里唤醒,继续执行
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) //D02. 线程没有被中断 interruptMode=0,继续while循环
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // D04. acquireQueued 抢夺锁,返回中断标识 interrupted为false
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled D05. nextWaiter为null
unlinkCancelledWaiters();
if (interruptMode != 0) // D06. interruptMode为0, false,方法执行结束
reportInterruptAfterWait(interruptMode);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
//D04. acquireQueued 抢夺锁,返回中断标识 interrupted为false
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { // 自旋一次
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // 抢夺锁成功
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted; // 返回false
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); //D01-D06. 从await中被唤醒
return dequeue(); // D07. 从队列中取数据
} finally {
lock.unlock();
}
}
// D07. 从队列中取数据
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; // takeIndex=0, 取得数据 items[0]
items[takeIndex] = null; // 将数组下标为0的位置清空
if (++takeIndex == items.length) // 两个步骤: 第一步. takeIndex +1,下次取数据则从下标为1的位置取;第二步. 判断取数据下标是否越界,越界从0开始取;此处和存数据逻辑一致
takeIndex = 0;
count--; // 元素数量-1
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); // 取得一个数据库,队列肯定不为空,notFull条件成立,signal唤醒等待的put线程
return x; //D08. 返回取得的元素
}
UserServiceSync
while (isRunning) {
try {
User user = userQueue.take(); // 最终,线程唤醒并取得数据
sendPoints(user); // 执行本次逻辑
} catch (InterruptedException e) {
e.printStackTrace();
}
}
二、CountDownLatch
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。当线程之间有先后依赖关系和执行顺序要求时,都可以使用CountDownLatch。
CountDownLatch提供了两个方法,一个是 countDown,一个是 await, CountDownLatch初始化的时候需要传入一个整数,在这个整数倒数到 0 之前,调用了 await 方法的程序都必须要等待,然后通过 countDown 来倒数,当倒数到0时唤醒之前await的线程。
1. CountDownLatch基本使用 await、countDown
Demo1: 主线程依赖于其它线程的执行结果: 主线程等待三个子线程执行完毕,开始执行
import java.util.concurrent.CountDownLatch;
public class CountDownDemo {
// 主线程等待 三个子线程执行完毕,开始执行
public static void main(String[] args) throws
InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
System.out.println(""+Thread.currentThread().getName()+"- 执行中");
countDownLatch.countDown();
System.out.println(""+Thread.currentThread().getName()+"- 执行完毕");
},"t1").start();
new Thread(()->{
System.out.println(""+Thread.currentThread().getName()+"- 执行中");
countDownLatch.countDown();
System.out.println(""+Thread.currentThread().getName()+"- 执行完毕");
},"t2").start();
new Thread(()->{
System.out.println(""+Thread.currentThread().getName()+"- 执行中");
countDownLatch.countDown();
System.out.println(""+Thread.currentThread().getName()+"- 执行完毕");
},"t3").start();
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " 所有线程执行完毕");
}
}
... 运行结果
t1- 执行中
t2- 执行中
t2- 执行完毕
t1- 执行完毕
t3- 执行中
t3- 执行完毕
main 所有线程执行完毕
Demo2: 100个子线程依赖于主线程的执行结果: 100个子线程等待主线程执行完毕开始执行
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo implements Runnable {
static CountDownLatch latch = new CountDownLatch(1);
// 100个子线程等待主线程执行完毕开始执行
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new CountDownLatchDemo()).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " do something");
latch.countDown();
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
... 运行结果
main do something
Thread-12
Thread-8
Thread-7
Thread-18
Thread-9
Thread-10
Thread-...
...
100个线程名
...
主线程中countDown,100个子线程中await,当主线程执行完处理逻辑后100个线程全部唤醒来执行,这100个线程并没有先后顺序,不会说第一个线程执行完去唤醒第二个线程来执行。
从这个使用场景来说,和Thread.join()有些相似,不过比它更加灵活。
2. 源码分析
以上面的 100个子线程等待主线程执行完毕 为例,主要有几个步骤:
a. 首先创建CountDownLatch
b. 之后,100个线程调用`latch.await()`等待
c. 主线程内休眠结束,`latch.countDown();`释放锁
d. `latch.countDown();`内unpark唤醒AQS队列中的共享节点
a. 首先创建CountDownLatch CountDownLatch countDownLatch = new CountDownLatch(1);
java.util.concurrent.locks.AbstractQueuedSynchronizer#setState
// 底层逻辑仍是使用AQS同步队列,将抢占锁的标识 state 设置为 1; 如果有N个前置条件线程则sate设置为N
protected final void setState(int newState) {
state = newState;
}
b. 之后,100个线程调用latch.await()
等待
会将这100个线程都封装为SHARED
共享类型的节点放入AQS同步队列中去,之后阻塞这100个线程
CountDownLatchDemo
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
// 抢占共享锁
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // B01. 试图抢占锁,判断state是否可用,为0则抢占成功,此时state为1, 抢占失败
doAcquireSharedInterruptibly(arg); // B02. 将当前线程需要加入到共享锁队列中
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
//B02. do抢占共享锁
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // B03. 创建一个SHARED类型的节点,加入AQS同步队列, 100个线程的话就是有101个Node节点的双向链表,首位head为默认创建的 new Node()
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg); // B04. state=1,此时不可能抢占成功, r = -1
if (r >= 0) { // r = -1, false
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && // B05. 抢占失败是否应该挂起 true
parkAndCheckInterrupt()) // B06. 最终,100个线程都会挂起
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt
// 阻塞线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // B07. 线程挂起
return Thread.interrupted();
}
c. 主线程内休眠结束,latch.countDown();
释放锁 主要逻辑doReleaseShared
:
tip:这里是释放的共享锁
共享锁的释放和独占锁的释放有一定的差别,前面唤醒锁的逻辑和独占锁是一样,先判断头结点是不是SIGNAL 状态,如果是,则修改为 0,并且唤醒头结点的下一个节点
PROPAGATE : 标识为 PROPAGATE 状态的节点,是共享锁模式下的节点状态,处于这个状态下的节点,会对线程的唤醒进行传播
java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 会将state-1,之后判断state等不等于0,等于0则表示共享锁已被释放,此处state为1,释放一次即可,返回true
doReleaseShared();
return true;
}
return false;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#doReleaseShared
// 释放共享锁
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) { // 自旋, 先从第一次自旋来说
Node h = head; // 取得头结点,为之前100个线程await的head,
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); // 唤醒头结点的下一节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break; // 退出自旋
}
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
// 唤醒头结点的下一节点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
d. latch.countDown();
内unpark唤醒AQS队列中的共享节点,从之前await
的park处开始继续执行
java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // D01. 之前的100个线程都是在这里阻塞,现在c步骤countDown唤醒了第一个节点
return Thread.interrupted();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) { // 自旋
final Node p = node.predecessor(); // D03. 当前节点的上一节点,为head
if (p == head) {
int r = tryAcquireShared(arg); // 试图抢占锁,根据state判断, 此处成功返回 1
if (r >= 0) {
setHeadAndPropagate(node, r); // D04. 重新设置头结点、并对线程的唤醒进行传递 (即唤醒当前shared的线程后,继续唤醒后续shared类型的线程)
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) D02. 线程唤醒,处于自旋内,开始下次自旋 -> D03
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#setHeadAndPropagate
// D04. 重新设置头结点、并对线程的唤醒进行传递
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node); // D05. 将当前节点设置为头结点
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 || // propagate为1 true
(h = head) == null || h.waitStatus < 0) {
Node s = node.next; // 得到当前下一节点
if (s == null || s.isShared()) // 100个线程都是 shared的, s.isShared() 为true
doReleaseShared(); // D06. 唤醒后续节点、共享锁的传递机制
}
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#setHead
// D05. 将当前节点设置为头结点
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#doReleaseShared
// D06. 唤醒后续节点、共享锁的传递机制
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) { // 自旋
Node h = head; // 即为100个线程中的第一个线程节点
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); // D07. 唤醒h节点的下一节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
// D07. 唤醒下一节点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // D08. 唤醒100个线程的第二个线程
}
... 至此,又回到d步骤的 D01 步,唤醒100个线程中的第二个线程,此时head为第一个节点,则第二个线程节点可以抢夺锁,标记为head节点,唤醒第三个线程; 第三个线程一看Head为第二个线程,抢夺锁并将自己标记为head,唤醒后续线程 ... 就这样shared的共享锁节点传递了下去
三、Semaphore(信号灯)
Semaphore 也就是我们常说的信号灯,semaphore 可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流的作用。
叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。
1. Semaphore基本使用 acquire、release
Demo: 10辆车,5个停车位
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
for (int i = 1; i < 11; i++) {
new Thread(new Car(i, semaphore)).start();
}
}
}
class Car implements Runnable {
int i;
Semaphore semaphore;
public Car(int i, Semaphore semaphore) {
this.i = i;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
// 申请许可
semaphore.acquire();
System.out.println(String.format("第 %s 辆车获得许可,正在使用停车位 %s", i, new Date()));
TimeUnit.SECONDS.sleep(i);
// 释放
System.out.println(String.format("第 %s 辆车驶离,释放停车位 %s", i, new Date()));
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
... 运行结果
第 4 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:32 CST 2019
第 5 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:32 CST 2019
第 1 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:32 CST 2019
第 2 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:32 CST 2019
第 3 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:32 CST 2019
第 1 辆车驶离,释放停车位 Mon Jun 01 21:28:33 CST 2019
第 6 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:33 CST 2019
第 2 辆车驶离,释放停车位 Mon Jun 01 21:28:34 CST 2019
第 7 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:34 CST 2019
第 3 辆车驶离,释放停车位 Mon Jun 01 21:28:35 CST 2019
第 8 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:35 CST 2019
第 4 辆车驶离,释放停车位 Mon Jun 01 21:28:36 CST 2019
第 9 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:36 CST 2019
第 5 辆车驶离,释放停车位 Mon Jun 01 21:28:37 CST 2019
第 10 辆车获得许可,正在使用停车位 Mon Jun 01 21:28:37 CST 2019
第 6 辆车驶离,释放停车位 Mon Jun 01 21:28:39 CST 2019
第 7 辆车驶离,释放停车位 Mon Jun 01 21:28:41 CST 2019
第 8 辆车驶离,释放停车位 Mon Jun 01 21:28:43 CST 2019
第 9 辆车驶离,释放停车位 Mon Jun 01 21:28:45 CST 2019
第 10 辆车驶离,释放停车位 Mon Jun 01 21:28:47 CST 2019
2. 源码分析
Semaphore的实现也是基于AQS同步队列以及共享锁的传递机制;在创建 Semaphore 实例的时候,需要一个参数 permits 这个是设置给 AQS 的 state 的,然后每个线程调用 acquire 的时候,
执行 state = state-1 ; release的时候执行 state = state + 1。当然 acquire 的时候,如果 state = 0 ,说明没有资源了,会将线程封装为 SHARED 共享类型的 Node 封装与AQS同步队列中去,等待其他线程 release 。
三、CyclicBarrier
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。
CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量parties,每个线程调用 await 方法告诉 CyclicBarrier 当前线程已经到达了屏障 parties-1,然后当前线程被阻塞;
当某个线程调用await达到 parties-1==0时,表明全部线程达到同步点,会唤醒之前的阻塞节点。
1. 基本使用
赛马场在一个环形赛道举行赛马比赛,5匹马,当所有5匹马同时回到起点代表一轮比赛结束;之后在起点重新出发
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class Horse implements Runnable{
CyclicBarrier cyclicBarrier;
public Horse(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// do something
TimeUnit.SECONDS.sleep(new Random().nextInt(4));
System.out.println(Thread.currentThread().getName() + " 已到屏障");
cyclicBarrier.await();
System.out.println(" ------------ 全部到达终点,休息后开始下一轮比赛");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
for (int i = 1; i <= 5; i ++) {
new Thread(new Horse(cyclicBarrier), "horse_" + i).start();
}
}
}
... 运行结果
horse_5 已到屏障
horse_4 已到屏障
horse_3 已到屏障
horse_1 已到屏障
horse_2 已到屏障
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
horse_2 已到屏障
horse_4 已到屏障
horse_3 已到屏障
horse_1 已到屏障
horse_5 已到屏障
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
horse_5 已到屏障
horse_2 已到屏障
horse_4 已到屏障
horse_1 已到屏障
horse_3 已到屏障
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
------------ 全部到达终点,休息后开始下一轮比赛
horse_2 已到屏障
...
2. 原理分析
这些并发工具原理大致相似,底层仍旧是依赖于AQS同步队列和Condition等待队列,对共享变量进行修改判断,不符合的话放入队列中并阻塞,符合的话从队列中唤醒。
四、Atomic原子操作
原子性这个概念,在多线程编程里是一个老生常谈的问题。所谓的原子性表示一个或者多个操作,要么全部执行完,要么一个也不执行。不能出现成功一部分失败一部分的情要么一个也不执行。不能出现成功一部分失败一部分的情况。
在多线程里面,要实现原子性,有几种方法,其中一种就是加 Synchronized 同步锁。而从JDK 1.5 开始,在 J .U.C 包中提供了 Atomic 包,提供了对于常用数据结构的原子操作。它提供了简单、高效、以及线程安全的更新一个变量的方式。
由于变量类型的关系,在J.U.C 中提供了 12 个原子操作的类。
类型 | 一个普通标题 |
---|---|
原子更新基本类型 | AtomicBoolean 、 AtomicInteger 、 AtomicLong |
原子更新数组 | AtomicIntegerArray 、 AtomicLongArray 、AtomicReferenceArray |
原子更新引用 | AtomicReference、 AtomicRef erenceFieldUpdater 、AtomicMarkableReference (更新带有标记位的引用类型) |
原子更新字段 | AtomicIntegerFieldUpdater、 AtomicLongFieldUpdater 、AtomicStampedReference |
1. 基本使用
创建1000个线程同时对基本类型i和原子类型num进行+1操作:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo {
static int i = 0;
static AtomicInteger num = new AtomicInteger();
public static void inc() {
i++;
}
public static void incAtomic() {
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(()-> {
AtomicDemo.inc();
AtomicDemo.incAtomic();
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i : " + i);
System.out.println("atomic i : " + num.get());
}
}
... 运行结果
i : 1000 或者 998 或者 ...
atomic i : 1000
原子类型的AtomicInteger类型运行结果一定是1000,而非原子操作结果时<=1000;
2. 原理分析
本质上Atomic的原子操作是使用乐观锁,CAS进行替换。
getAndIncrement 实际上是调用 unsafe 这个类里面提供的方法,Unsafe类相当于是一个后门,使得 Java 可以像 C 语言的指针一样直接操作内存空间。当然也会带来一些弊端,就是指针的问题。实际上这个类在很多方面都有使用,除了 J .U.C 这个包以外,还有Netty、kafka 等等。这个类提供了很多功能,包括多线程同步(monitorEnter) 、CAS 操作(compareAndSwap)、线程的挂起和恢复(park/unpark) 、内存屏障 (loadFence/ storeFence)、内存管理(内存分配、释放内存、获取内存地址等)
java.util.concurrent.atomic.AtomicInteger#getAndIncrement
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
sun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // 乐观锁 CAS替换成功则退出,否则继续取得最新值,加上var4后继续替换
return var5;
}
sun.misc.Unsafe#compareAndSwapInt
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);