新建一个线程池


ExecutorService


提交一个任务执行


Future


提交之后就会执行。

使用CountDownLatch进行线程控制

使用场景为,有一个任务,必须等待前置的N个任务完成,方能执行,那么使用CountDownLatch非常合适。

我们看下面这一个例子


//做线程池


我们新建了一个线程池,使用CountDownLatch来控制线程阻塞(遇到countDownLatch.await()方法即阻塞,直到countDownLatch的值为0)

线程A等待3秒,打印准备结束日志,然后将countDownLatch减1(得0)

线程B先打印一句准备执行,之后等待countDownLatch为0,再打印一句结束。

提交之后,主线程等待10秒钟,等待2个线程完成。


14:12:03.775 [pool-1-thread-2] INFO com.example.demo.CountDownTest - B 准备等待countDown对象,当前值: 1
14:12:03.775 [pool-1-thread-1] INFO com.example.demo.CountDownTest - A 线程先执行,3秒后将countDown - 1
14:12:06.785 [pool-1-thread-1] INFO com.example.demo.CountDownTest - A  准备将countDownLatch对象-1,当前: 1
14:12:06.785 [pool-1-thread-1] INFO com.example.demo.CountDownTest - A  将countDownLatch对象置为了:0
14:12:06.785 [pool-1-thread-2] INFO com.example.demo.CountDownTest - B 获取到了countDown对象,当前值:0


executorService


使用该方法可以使得主线程不会永远等待。

值得注意的是,这个对象是一次性的,count数目不能重置。

使用CyclicBarrier进行线程控制

与CountDownLatch类似。但是可以重置

示例代码:


long


输出如下:


14:49:44.618 [pool-1-thread-1] - Task 1 ready to wait the Key Task, with 3 parties, with 0 numberWaiting
14:49:45.613 [pool-1-thread-2] - Task 2 ready to wait the Key Task, with 3 parties, with 1 numberWaiting
14:49:46.613 [pool-1-thread-3] - Task 3 ready to wait the Key Task, with 3 parties, with 2 numberWaiting
14:49:46.613 [pool-1-thread-3] - the Key Task execute now
14:49:49.618 [pool-1-thread-3] - the Key Task done
14:49:49.631 [pool-1-thread-1] - Task 1 can continue, with 3 parties, with 0 numberWaiting, 2
14:49:49.642 [pool-1-thread-2] - Task 2 can continue, with 3 parties, with 0 numberWaiting, 1
14:49:49.653 [pool-1-thread-3] - Task 3 can continue, with 3 parties, with 0 numberWaiting, 0


如果这里将线程池的数目设置为2,则会一直等待下去。

可以看出这个类的使用意义在于,如果你有3个任务,他们执行到


int


这一句之后,会触发cyclicBarrier执行回调任务,该任务完成后,3个线程才会继续往下执行。

Semaphore模拟资源进行控制

其实前面2个方法已经提供了良好的控制了,这里又提供了一个工具,Semaphore,模拟资源,来协调各个线程的运行。

假设我们有一个数据库,连接池只有3,但我们同时需要进行N次查询,那么我们就可以使用这个类来方便的控制。

新建一个线程池用于查询


ExecutorService


使用Semaphore来模拟数据库连接池


Semaphore


这是非常常见的使用用例,各个线程在进行SQL查询时,先去尝试获取连接池,如果没有资源了,就等待一个别的线程使用完毕,多余一个出来。


//一共5个DB查询任务


并发查询是工业级项目常有的需求,使用Semaphore非常方便!

输出如下:


15:00:57.531 - Task 1 try for DB LINK: 2
15:00:57.531 - Task 2 try for DB LINK: 2
15:00:57.531 - Task 5 try for DB LINK: 2
15:00:57.531 - Task 4 try for DB LINK: 2
15:00:57.531 - Task 3 try for DB LINK: 2

15:00:57.537 - Task 1 get DB LINK
15:00:57.537 - Task 2 get DB LINK

15:00:58.541 - Task 1 ready to release DB LINK
15:00:58.541 - Task 5 get DB LINK

15:00:59.540 - Task 2 ready to release DB LINK
15:00:59.540 - Task 3 fail to get DB LINK
15:00:59.540 - Task 4 fail to get DB LINK
15:01:03.546 - Task 5 ready to release DB LINK


我把输出结果优化了一下:

第一个输出块,可以看出,5个线程同时尝试获取limit为2的DB连接

第二个输出块,线程1,2获取到了DB连接,1,2开始执行任务

第三个输出块,线程1完成了任务,准备释放DB连接,于是线程5获取到了DB连接

第四个输出块,线程2也完成了任务,但是来不及了,达到了设定的最大等待时间,线程3,4未能完成任务,最后线程5完成释放,但是没有新的线程可以获取DB连接了。

使用Phaser进行多阶段的并发控制

想象我们现在需要把大象装进冰箱,众所周知需要三个阶段

那么Phaser相当于一个指挥,他指挥3个线程分别将各自的大象装进各自的冰箱,要求:

所有的线程都打开冰箱门后,才能开始装大象

所有的大象都装进冰箱后,才能关冰箱门

资源准备:


ExecutorService


这里的Phaser是自己继承的一个类,重写了一个回调的方法


@Slf4j


三个线程准备:


for


开始调度:


log


打印如下:


17:23:47.371 [main] ConcurrentTest -  ----- 开始把大象装进冰箱
17:23:47.374 [thread-2] ConcurrentTest - 1线程 已经打开冰箱门,等待其他线程
17:23:47.374 [thread-3] ConcurrentTest - 2线程 已经打开冰箱门,等待其他线程
17:23:47.374 [thread-4] ConcurrentTest - 3线程 已经打开冰箱门,等待其他线程
17:23:47.374 [thread-4] demo.MyPhaser - now is 0  register is 3

17:23:48.375 [thread-2] ConcurrentTest - 1线程 大象装进冰箱了,等待其他线程

17:23:49.378 [thread-3] ConcurrentTest - 2线程 大象装进冰箱了,等待其他线程

17:23:50.378 [thread-4] ConcurrentTest - 3线程 大象装进冰箱了,等待其他线程
17:23:50.378 [thread-4] demo.MyPhaser - now is 1  register is 3

17:23:51.383 [thread-2] ConcurrentTest - 1线程 冰箱门已关闭,结束流程
17:23:52.381 [thread-3] ConcurrentTest - 2线程 冰箱门已关闭,结束流程

17:23:53.379 [thread-4] ConcurrentTest - 3线程 冰箱门已关闭,结束流程
17:23:53.379 [thread-4] MyPhaser - now is 2  register is 0

17:23:56.398 [main] ConcurrentTest - final phaser -2147483645


BlockingQueue

阻塞队列是为了满足多线程之间共享数据使用的。

我在此前没有接触过在同一个JVM内,需要使用这种设计的模型,接触到这个比较难懂,放一个中文讲的很好的博客

BlockingQueue - 不会就问咯 - 博客园www.cnblogs.com


Java 多线程内套多线程_数据共享


多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。
在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。