前言苏宁估计是今年最大的黑马了,我们在开始中没有期望能战胜G2小组第一出线,在八进四中我们也没有没想到苏宁能够打赢京东,结果苏宁3:1战胜京东,我们认为苏宁今年的世界赛的成绩已经很好了,或许也就止步于此了,毕竟他作为LPL三号种子,接下来要面对的是一号种子Tes,而且Tes刚经历了一场让二追三的血腥厮杀,状态火热。苏宁用实力告诉你,不,我要追求更高的目标,他们战胜了Tes,成为了LPL 唯一的希望。\
现在我们唯一的期待,10月31号,我们能听到一句恭喜苏宁,恭喜LPL!
这波,感谢一波大校,没问题吧!周六大校支持一波大乌龟没问题吧
感谢你,苏小落回归主题,今天给大家分享个简单的知识点,JDK并发包里面自带的一些工具类,功能很强大实用
CountDownLatch
CountDownLatch就像一个门,门上有N把锁,只有当锁同时都打开,我们才能开门。这里这个门是一次性的,用完之后不能重新再给这个门上锁,为什么我要强调一次性呢?因为下面还有不是一次性的CyclicBarrier
CountDownLatch,使一个线程等待其他线程都达到相应的状态再执行,完成工作的主要方法就是await()、countDown()
内部是通过一个计数器实现的,计数器的初始值就是要等待线程的数量,每当一个线程执行完毕调用countDown()之后计数器数值减一,计数器数值变为0的时候,表示所有线程执行完毕,调用await()方法等待的线程便会恢复工作
来看一个例子:
public class TestCountDownLatch {
public static void main(String[] args) {
// 传入参数2,代表需要等待两个线程
final CountDownLatch latch = new CountDownLatch(2);
//线程等待一秒
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("一号准备完毕,用时1秒!");
latch.countDown();
}
}).start();
//线程等待三秒
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("二号准备完毕,用时2秒!");
latch.countDown();
}
}).start();
try {
System.out.println("两位选手请准备!");
//主线程调用await,等待两个线程到达(即调用countDown)
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 两个线程执行完毕后,主线程恢复运行
System.out.println("准备完毕,开始!");
}
}
看下运行结果:
两位选手请准备!
一号准备完毕,用时1秒!
二号准备完毕,用时2秒!
准备完毕,开始!
其实看到这里大家应该都懂了,没啥可说的,太简单了,接下来一起简单分析下源码,了解下它是如何实现的吧
CountDownLatch是基于AQS的同步器
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
看下CountDownLatch的构造函数,就是传入一个int数值,这个数值就是状态变量,点击Sync()发现是一个setState的设置状态的函数,再点进去发现进去的是AQS的一个方法,count就是AQS内部的一个volatile变量
/**
* The synchronization state.
*/
private volatile int state;
volatile代表着啥我就不用多说了吧,还不清楚的传送门在这里学习volatile这一篇就够了,保证了可见性,有序性\
我们看下内部的await()和counDown()的实现方法:\
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
发现内部的实现都是通过sync实现的,这个sync对象时何方神圣呢?\
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer { }
哦,原来如此,是AQS的实现类啊原来,也就是CountDownLatch内部的主要方法是通过AQS来实现的\
前面的我是看懂了,但是这个AQS到底是个什么东西呢?AQS是一个多线程访问共享资源的同步器框架,资源共享的方式有两种,即独占Exclusive和共享Share;独占即只有一个线程能够执行,控制并发安全,例如ReentrantLock,共享即多个线程可以同时执行,比如我们现在说的CountDownLatch
内部通过维护了一个state共享资源和一个FIFO线程等待队列来实现的,自定义同步器时只需要实现共享资源state的获取和释放的方式即可。
CyclicBarrier
上面说到了一次性的锁,有时我们可能需要重复设置这个锁,这个场景如何满足呢?CyclicBarrier可以满足这个场景,这个工具类是可以重复使用的,通过reset来设置即可
和CountDownLatch不同的是,CyclicBarrier在指定数量的线程到达之前必须互相等待,也是因为在等待的线程被释放之后可以重复使用
在网上看到的一种说法叫做人满发车,很合适,车没有满的时候车上乘客需要等待,车到达目的地之后再返回出发点,重新等待发车
看个例子:
public class TestCyclicBarrier {
public static void main(String[] args) {
//初始化四个线程
int threadNum = 4;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new MyThread());
for (int i = 0; i < threadNum; i++) {
new TestThread(barrier).start();
}
}
static class TestThread extends Thread {
private CyclicBarrier cyclicBarrier;
public TestThread(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在执行");
try {
Thread.sleep(3000); //以睡眠来模拟操作
System.out.println("线程" + Thread.currentThread().getName() + "执行完毕,等待其他线程执行完成");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有线程执行完成,继续处理其他任务...");
}
}
static class MyThread extends Thread {
@Override
public void run() {
System.err.println("我是特殊任务");
}
}
}
输出结果如下:\
线程Thread-3正在执行
线程Thread-2正在执行
线程Thread-4正在执行
线程Thread-1正在执行
线程Thread-1执行完毕,等待其他线程执行完成
线程Thread-2执行完毕,等待其他线程执行完成
线程Thread-3执行完毕,等待其他线程执行完成
线程Thread-4执行完毕,等待其他线程执行完成
所有线程执行完成,继续处理其他任务...
所有线程执行完成,继续处理其他任务...
所有线程执行完成,继续处理其他任务...
所有线程执行完成,继续处理其他任务...
我是特殊任务
CyclicBarrier内部是通过一个ReentrantLock的锁对象来控制的,基于Condition条件队列来对线程进行阻塞
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
内部也是一个计数器,每当线程到达屏障点的时候调用await()将自己阻塞,计数器减一,当计数器变为0的时候所有因为调用await()方法阻塞的线程都会被唤醒\
CyclicBarrier和CountDownLatch的不同点:\
1、CountDownLatch是调用await的线程等待,而CyclicBarrier是大家一起等,互相等待;
2、CountDownLatch是一次性的,而CyclicBarrier是可以重复使用的;
在一定程度上,CyclicBarrier和CountDownLatch是相似的,在一些场景下两者均可实现,比如当多个线程共同达到同一条件,一起继续执行这种,两者均可实现
Semaphore
我们在商场找停车位,车位一般是固定的,车位属于共享资源,更多的车子可能会对这些固定数量的车位进行“抢夺”,这种情景好像上面两个工具类都不太好解决,JDK中提供了信号量Semaphore这个工具类,用于在多线程环境下能够协调各个线程对共享资源的正确、合理使用。
初始化的时候需要为这个许可集传入一个数值,这个数值代表同一时刻能够访问共享资源的线程数量,线程通过acquire()获得一个许可,然后对共享资源进行操作,如果许可集分配完了,线程进入等待状态,知道其他线程释放许可才有机会获得许可。
看一个例子:
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//3个停车位
for (int i = 0; i <6 ; i++) {//模拟6部汽车
new Thread(()->{
try {
semaphore.acquire();//占到车位
System.out.println(Thread.currentThread().getName()+" 抢占到车位");
TimeUnit.SECONDS.sleep(5);//模拟车停5秒
System.out.println(Thread.currentThread().getName()+" 停车3秒后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放停车位
}
},String.valueOf(i)).start();
}
}
}
输出结果是:\
2 抢占到车位
3 抢占到车位
1 抢占到车位
2 停车3秒后离开车位
3 停车3秒后离开车位
1 停车3秒后离开车位
0 抢占到车位
4 抢占到车位
5 抢占到车位
4 停车3秒后离开车位
0 停车3秒后离开车位
5 停车3秒后离开车位
即初始化停车位数量(许可集),每个车子代表一个线程,进入停车场会获得一个许可,占用共享资源。一旦停车位全部被占,未分配到车位的车子进入等待状态,等其它车子释放车位才有机会获得车位
Semaphore内部也是通过AQS实现的,当许可集的数量设置成1的时候可以来做一个互斥锁,感兴趣的同学自己去研究发觉
Exchanger
还有一个常见的工具类就是Exchanger,这个我们听名字估计就能猜出大概,但凡英语过个四级这个单词应该都认识
Exchanger,就是提供了两个线程互相交换数据的同步点,即一个线程完成一定的事务之后想要和另一个线程交换数据,则需要拿出数据,等待另一个线程的到来。
public class TestExchanger {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
String book = "《Java核心技术卷一》";
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("甲带着" + book + "到达交易地点!");
try {
System.out.println("甲换出了:" + book + ",换回了:" + exchanger.exchange(book));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String money = "200元";
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乙带着" + money + "到达交易地点!");
try {
System.out.println("乙换出了:" + money + ",换回了:" + exchanger.exchange(money));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
输出结果:
甲带着《Java核心技术卷一》到达交易地点!
乙带着200元到达交易地点!
甲换出了:《Java核心技术卷一》,换回了:200元
乙换出了:200元,换回了:《Java核心技术卷一》
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的
好了,以上就是全部内容了,我是,你们的学习成长小伙伴
我希望有一天能够靠写字养活自己,现在还在磨练,这个时间可能会有很多年,感谢你们做我最初的读者和传播者。请大家相信,只要给我一份爱,我终究会还你们一页情的。
再次感谢大家能够读到这里,我后面会持续的更新技术文章以及一些记录生活的灵魂文章,如果觉得不错的,觉得有点东西的话,求点赞、关注、分享三连
哦,对了!后续的更新文章我都会及时放到这里,欢迎大家点击观看,都是干货文章啊,建议收藏,以后随时翻阅查看
推荐阅读