线程同步工具主要包括:
- 闭锁(CountDownLatch)
- 栅栏(CyclicBarrier)
- 信号量(Semaphore)
- 阻塞队列(BlockingQueue)
- FutureTask
CountDownLatch
可以让其他线程先执行,等执行完成之后再执行当前线程。CountDownLatch的构造方法传入了一个整数,相当于计数器,每次调用CountDownLatch的countDown()方法,计数器减一,而调用await()会阻塞当前线程,直到计数器为0后,才执行await()之后的代码。
使用场景
统计报表的优化,一般统计报表都需要从多个表中将数据统一展示出来,有时候遇上数据量比较大的情况,统计指标涉及到的业务范围也比较多,这个时候接口响应速度就会很慢,然后就可以使用CountDownLatch,将每个查询任务拆分到单独的线程中去执行,执行完成后调用countDown()方法,主线程调用await()等待所有任务完成之后进行汇总,这样使得原来串行化的任务变成了并行,效率自然提高上去了。
下面的代码初始化了一个线程数为10的线程池,并且将CountDownLatch的计数器设置为相同大小,当线程池中的任务执行完毕之后,主线程才开始往下执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class CountDownLatchTest { public static void main(String[] args) { int count = 10; CountDownLatch countDownLatch = new CountDownLatch(count); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(count);
for (int i = 0; i < count; i++) { CountDownLatchRunnable runnable = new CountDownLatchRunnable((i + 1) * 1000,countDownLatch); fixedThreadPool.execute(runnable); } try { countDownLatch.await(); System.out.println("所有runnable执行完毕,开始向下执行"); } catch (InterruptedException e) { e.printStackTrace(); }finally { fixedThreadPool.shutdown(); } }
public static class CountDownLatchRunnable implements Runnable { private long sleepTime; private CountDownLatch latch; public CountDownLatchRunnable(long sleepTime, CountDownLatch countDownLatch) { this.sleepTime = sleepTime; this.latch = countDownLatch; } @Override public void run() { try { System.out.println(Thread.currentThread().getName()+"线程开始执行"); TimeUnit.MILLISECONDS.sleep(sleepTime); latch.countDown(); System.out.println(Thread.currentThread().getName()+"线程执行完毕"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pool-1-thread-1线程开始执行 pool-1-thread-2线程开始执行 pool-1-thread-3线程开始执行 pool-1-thread-8线程开始执行 pool-1-thread-4线程开始执行 pool-1-thread-9线程开始执行 pool-1-thread-5线程开始执行 pool-1-thread-10线程开始执行 pool-1-thread-6线程开始执行 pool-1-thread-7线程开始执行 pool-1-thread-1线程执行完毕 pool-1-thread-2线程执行完毕 pool-1-thread-3线程执行完毕 pool-1-thread-4线程执行完毕 pool-1-thread-5线程执行完毕 pool-1-thread-6线程执行完毕 pool-1-thread-7线程执行完毕 pool-1-thread-8线程执行完毕 pool-1-thread-9线程执行完毕 pool-1-thread-10线程执行完毕 所有runnable执行完毕,开始向下执行
|
CyclicBarrier
等待其他线程同时到达(会阻塞当前线程),所有线程同时到达后,才继续往下执行。CyclicBarrier有两个构造方法:
1 2
| public CyclicBarrier(int parties) public CyclicBarrier(int parties, Runnable barrierAction)
|
第一个参数是线程的数量,也就是说当有这么多个线程到达之后,所有线程才继续往下执行。第二个参数是一个Runnable,也就是说,所有线程到达之后,会执行这个Runnable,你可以在它的run方法里做一些处理。
使用场景
不难看出,CyclicBarrier可以实现和CountDownLatch一样的效果,不过CyclicBarrier有一个reset方法,可以重复利用,而CountDownLatch只能用一次。而且CyclicBarrier可以在做了一些操作之后各个线程继续往下执行。
比如说,你和几个朋友约好了,明天早上集合到你家,开车出去玩,第二天早上你得等你的朋友们都到齐上车了,才能开车出发。出发之前还可以一起吃个早饭,而CountDownLatch是做不到的。

下面的代码初始化了一个线程数为10的线程池,将CyclicBarrier的线程数大小也设置为10,这样线程到达cyclicBarrier.await()的时候都会被阻塞,直到最后一个线程到达:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class CyclicBarrierTest { public static void main(String[] args) { int count = 10; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(count); CyclicBarrier cyclicBarrier = new CyclicBarrier(count, new Runnable() { @Override public void run() { System.out.println("所有线程已经到达,可以在这里面做一些东西"); } }); for (int i = 0; i < count; i++) { CyclicBarrierRunnable runnable = new CyclicBarrierRunnable((i + 1) * 1000,cyclicBarrier); fixedThreadPool.execute(runnable); } }
public static class CyclicBarrierRunnable implements Runnable{ private CyclicBarrier cyclicBarrier; private long sleepTime;
public CyclicBarrierRunnable(long sleepTime, CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; this.sleepTime = sleepTime; }
@Override public void run() { try { System.out.println(Thread.currentThread().getName() + " 线程开始执行任务"); TimeUnit.MILLISECONDS.sleep(sleepTime); System.out.println(Thread.currentThread().getName() + " 到达珊阑处,等待其他线程到达"); cyclicBarrier.await(); System.out.println(Thread.currentThread().getName() + " 所有线程到达,开始向下执行"); } catch (BrokenBarrierException e) { e.printStackTrace(); }catch (InterruptedException e){ e.printStackTrace(); } } } }
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| pool-1-thread-2 线程开始执行任务 pool-1-thread-3 线程开始执行任务 pool-1-thread-4 线程开始执行任务 pool-1-thread-1 线程开始执行任务 pool-1-thread-6 线程开始执行任务 pool-1-thread-5 线程开始执行任务 pool-1-thread-8 线程开始执行任务 pool-1-thread-9 线程开始执行任务 pool-1-thread-7 线程开始执行任务 pool-1-thread-10 线程开始执行任务 pool-1-thread-1 到达珊阑处,等待其他线程到达 pool-1-thread-2 到达珊阑处,等待其他线程到达 pool-1-thread-3 到达珊阑处,等待其他线程到达 pool-1-thread-4 到达珊阑处,等待其他线程到达 pool-1-thread-5 到达珊阑处,等待其他线程到达 pool-1-thread-6 到达珊阑处,等待其他线程到达 pool-1-thread-7 到达珊阑处,等待其他线程到达 pool-1-thread-8 到达珊阑处,等待其他线程到达 pool-1-thread-9 到达珊阑处,等待其他线程到达 pool-1-thread-10 到达珊阑处,等待其他线程到达 所有线程已经到达,可以在这里面做一些东西 pool-1-thread-10 所有线程到达,开始向下执行 pool-1-thread-1 所有线程到达,开始向下执行 pool-1-thread-2 所有线程到达,开始向下执行 pool-1-thread-3 所有线程到达,开始向下执行 pool-1-thread-4 所有线程到达,开始向下执行 pool-1-thread-5 所有线程到达,开始向下执行 pool-1-thread-6 所有线程到达,开始向下执行 pool-1-thread-7 所有线程到达,开始向下执行 pool-1-thread-9 所有线程到达,开始向下执行 pool-1-thread-8 所有线程到达,开始向下执行
|
Semaphore
通过构造方法可以指定可用资源数量,线程在执行的时候,通过semaphore.acquire()获取资源,如果获取到,继续执行,如果获取不到,就会阻塞当前线程,直到其他线程通过semaphore.release()释放资源。
使用场景
池化技术 比如对象池 连接池 缓存池等。多个线程获取有限资源的情况
下面的代码,十个线程抢占三个可用资源,剩下的7个线程会被阻塞住,直到资源使用完毕:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class SemphoreTest {
public static void main(String[] args) { int count = 10; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(count); Semaphore semaphore = new Semaphore(3); for (int i = 0; i < count; i++) { SemphoreRunnable runnable = new SemphoreRunnable((i + 1) * 1000,semaphore); fixedThreadPool.execute(runnable); } }
public static class SemphoreRunnable implements Runnable{ private Semaphore semaphore; private long sleepTime;
public SemphoreRunnable(long sleepTime, Semaphore semaphore) { this.semaphore = semaphore; this.sleepTime = sleepTime; }
@Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 占用一个资源"); TimeUnit.MILLISECONDS.sleep(sleepTime); System.out.println(Thread.currentThread().getName() + "资源使用完毕,释放资源"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| pool-1-thread-2 占用一个资源 pool-1-thread-3 占用一个资源 pool-1-thread-6 占用一个资源 pool-1-thread-2资源使用完毕,释放资源 pool-1-thread-4 占用一个资源 pool-1-thread-3资源使用完毕,释放资源 pool-1-thread-5 占用一个资源 pool-1-thread-6资源使用完毕,释放资源 pool-1-thread-4资源使用完毕,释放资源 pool-1-thread-1 占用一个资源 pool-1-thread-7 占用一个资源 pool-1-thread-1资源使用完毕,释放资源 pool-1-thread-8 占用一个资源
|
BlockingQueue
阻塞队列提供可阻塞的入队和出队操作,队列满了,入队操作就会阻塞直到有其他元素出队,队列空了,出队操作会阻塞直到有新的元素可用。
BlockingQueue提供了一下几种操作:
1 2 3 4 5 6
| boolean add(E e); boolean offer(E e); void put(E e); E take(); E poll(); boolean remove(Object o);
|
其中,实现了BlockingQueue的接口有下面几个队列:
- ArrayBlockingQueue:数组实现的有界队列,初始化必须制定容量,并且可以选择是否需要公平性(通过设置ReentrantLock来实现公平锁)
- LinkedBlockingQueue :链表实现的有界队列,在不指定容量的时候,大小为Integer.MAX_VALUE。
- PriorityBlockingQueue :支持优先级的无界队列,元素按照优先级顺序移除,因为是无界的,所以put不会被阻塞
- DelayQueue :优先级支持的、基于时间调度的队列,元素只有在延迟期满了,才能从中获取到
- SynchronousQueue:简单聚集队列
下面使用ArrayBlockingQueue实现生产者-消费者模型,生产者每隔1秒生产一个产品,10个消费者消费产品,每隔1秒,只有一个消费者获取产品消费,其他消费者只能等待:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public class BlockQueueTest { public static void main(String[] args) { BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10); Executors.newSingleThreadExecutor().submit(new Producer(blockingQueue)); ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.submit(new Consumer(blockingQueue)); } }
public static class Consumer implements Runnable{ private BlockingQueue<String> blockingQueue; public Consumer(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; }
@Override public void run() { try { while (true){ TimeUnit.MILLISECONDS.sleep(1000); System.out.println("准备消费产品"); System.out.println(Thread.currentThread().getName() + " 开始消费产品 " + blockingQueue.take()); System.out.println(Thread.currentThread().getName() + "消费结束"); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public static class Producer implements Runnable{ private BlockingQueue<String> blockingQueue; public Producer(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; }
@Override public void run() { try { while (true){ TimeUnit.MILLISECONDS.sleep(1000); SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); String product = dateFormat.format(new Date()); System.out.println(Thread.currentThread().getName() + " 生产产品"); blockingQueue.put(product); } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } }
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 准备消费产品 准备消费产品 准备消费产品 准备消费产品 准备消费产品 准备消费产品 准备消费产品 准备消费产品 准备消费产品 准备消费产品 pool-1-thread-1 生产产品 pool-2-thread-3 开始消费产品 00:10:40 pool-2-thread-3消费结束 准备消费产品 pool-1-thread-1 生产产品 pool-2-thread-6 开始消费产品 00:10:41 pool-2-thread-6消费结束 准备消费产品 pool-1-thread-1 生产产品 pool-2-thread-1 开始消费产品 00:10:42 pool-2-thread-1消费结束
|
FutureTask
用于执行一个可返回结果的长任务,任务在单独的线程中执行,其他线程可以用 get 方法取任务结果,如果任务尚未完成,线程会阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class FutureTaskTest { public static void main(String[] args) { FutureTask<String> futureTask = new FutureTask((Callable<String>) () -> { TimeUnit.MILLISECONDS.sleep(1000); Random random = new Random(); boolean b = random.nextBoolean(); return b?"success":"fail"; }); new Thread(futureTask).start(); try { System.out.println("主线程执行其他业务。。。"); TimeUnit.MILLISECONDS.sleep(1000); System.out.println("开始获取子线程运行结果。。。"); String result = futureTask.get(); if ("success".equals(result)){ System.out.println("任务执行成功"); }else { System.out.println("任务执行失败"); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
|
执行结果:
1 2 3
| 主线程执行其他业务。。。 开始获取子线程运行结果。。。 任务执行失败
|