java.util.concurrent 包从 JDK1.5 开始引入,目的是解决并发编程的线程安全问题,提供非常有用的并发工具类,包括 CountDownLatch、CyclicBarrier 与 Semaphore 等。
在 concurrent 包下还有两个子包,一个是 atomic,包含一些原子类,可以解决原子性问题;另一个包是 locks,提供了并发包里线程安全的最为基础的工具——显示锁(ReentrantLock、ReadWriteLock)。
CountDownLatch使用
CountDownLatch 允许一个或多个线程等待其他线程完成操作,利用它可以实现类似计数器的功能。比如现在有一个线程,需要等待其它线程执行完之后才能执行,此时就可以使用 CountDownLatch 实现这种需求。
当然,也可以通过 join() 方法来实现,不过 CountDownLatch 实现的更为灵活、更为方便。
CountDownLatch 的主要方法如下:
- public CountDownLatch(int count):构造器,指定 count 值
- public void await():调用 await() 方法的线程会被挂起,直到 count 值为0继续执行
- public boolean await(long timeout, TimeUnit unit):调用 await() 方法的线程会被挂起,直到 count 值为0或指定时间后继续执行
- public void countDown():将计数值 count 减1
- public long getCount():返回 count 值
示例如下:
public class CountDownLatchDemo {
private static CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "正在执行");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "正在执行");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程执行完毕");
System.out.println("count值:" + latch.getCount());
System.out.println("继续执行主线程");
}
}
输出结果:
等待2个子线程执行完毕...
Thread-0正在执行
Thread-1正在执行
Thread-0执行完毕
Thread-1执行完毕
2个子线程执行完毕
count值:0
继续执行主线程
CountDownLatch 并没有提供设置 count 值的方法,只能在初始化时设置。当 count 值变为0时,如果还有第二次等待,还需要创建一个新的 CountDownLatch。
CyclicBarrier用法
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier),或者说回环栅栏。通过它可以实现让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会继续执行。每个线程调用 await() 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier 主要方法如下:
- public CyclicBarrier(int parties):构造器,参数为屏障拦截的线程数量
- public CyclicBarrier(int parties, Runnable barrierAction):构造器,参数为屏障拦截的线程数量、这些线程都到达同步点时会执行的内容
- public int await():调用 await() 方法的线程会被挂起,直至所有线程都到达同步点
- public int await(long timeout, TimeUnit unit):调用 await() 方法的线程会被挂起,直至所有线程都到达同步点或到达指定的时间
- public int getParties():返回设置的 parties 值
- public void reset():重置为初始状态
- 如果有线程已经处于等待状态,调用 reset() 方法会导致已经在等待的线程抛出异常,并且将会导致其它线程始终处于阻塞状态。
- 如果在等待的过程中,线程被中断(Interrupt),会抛出异常,并且会影响到其他所有的线程。
- 如果超出指定的等待时间,当前线程会抛出 TimeoutException 异常,其他线程会抛出 BrokenBarrierException 异常。
- 如果在执行屏障操作过程(barrierAction)中发生异常,会影响到其他所有的线程,抛出 BrokenBarrierException 异常,屏障被损坏。
示例如下:
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () ->
System.out.println(Thread.currentThread().getName() + ":所有线程写入完毕"));
for (int i = 0; i < cyclicBarrier.getParties(); i++)
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "正在写入数据...");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "写入数据完毕");
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
输出结果:
Thread-0正在写入数据...
Thread-1正在写入数据...
Thread-2正在写入数据...
Thread-0写入数据完毕
Thread-1写入数据完毕
Thread-2写入数据完毕
Thread-2:所有线程写入完毕
CountDownLatch 与 CyclicBarrier 的含义稍微有一点区别。
对于前者,重点是一个或多个线程等待,而其它的线程在完成“某件事情”之后,可以继续执行;对于后者,重点是在任意一个线程没有完成“某件事情”之前,所有的线程都必须等待。
除此之外,相比 CountDownLatch,CyclicBarrier 是可以重用的。
Semaphore用法
Semaphore,即计数信号量,通常用来限制可以同时访问某些资源的线程数量。
举一个例子来说,假如银行有4个服务窗口,现在有7个人同时想要办理业务,怎么办呢?只能是先进去4个人,另外3个人在大厅等候。只有前面的一个人办完业务,服务窗口有空闲,另外一个人才能进去办业务。这里服务窗口就相当于资源,4个人相当于被限制的线程数量。
Semaphore 的主要方法如下:
- public Semaphore(int permits):构造器,指定允许同时访问的许可数量,不遵循FIFO原则
- public Semaphore(int permits, boolean fair):构造器,指定允许同时访问的线程数量;如果fair为true,表示遵循FIFO原则
- public void acquire() throws InterruptedException:获得访问资源的权利(许可)
- public void release():释放访问资源的权利(许可)
- public int availablePermits():返回剩余可获得的许可数量
示例如下:
public class SemaphoreDemo {
private static Semaphore semaphore = new Semaphore(4);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 7; i++) {
int order = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(order + "号线程接入,目前剩余" + semaphore.availablePermits());
TimeUnit.SECONDS.sleep((long) (Math.random() * 10) + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();
System.out.println(order + "号线程释放");
}).start();
}
}
}
输出结果:
0号线程接入,目前剩余3
2号线程接入,目前剩余2
1号线程接入,目前剩余1
3号线程接入,目前剩余0
2号线程释放
4号线程接入,目前剩余0
1号线程释放
5号线程接入,目前剩余0
3号线程释放
6号线程接入,目前剩余0
0号线程释放
4号线程释放
6号线程释放
5号线程释放
注:因为多线程执行顺序不确定,因此多次执行的结果可能并不一致。
参考链接: