在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch、CyclicBarrier、Semaphore。
一、CountDownLatch
这个CountDownLatch的构造函数接受一个int类型的参数作为计数器,N表示阻塞的线程必须等待N次countDown才能执行
每次调用CountDownLatch的countDown方法时,N就会减1,而这个方法可以使用到任何地方,这里的N点可以是N个线程,也可以是一个线程的N个步骤
而CountDownLatch的await方法则会阻塞当前线程,直到N为0的时候才能执行。
线程的join方法,这个方法也是可以阻塞当前线程,等待某线程执行完成,通过对比,我们可以发现使用CountDownLatch这个工具类更灵活,因为countDown可以用
在任何线程的任何地方。
CountDownLatch适合一个大任务拆分成多个小任务,然后再所有子任务完成后,通知其他的后续操作开始执行。
二、同步屏障CyclicBarrier
CyclicBarrier默认的构造方法CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达屏障,然后当前线程被阻塞,直到被拦截的线程全部都到达了屏障,然后前边被阻塞的线程才能开始执行,否则会被一直阻塞。
CyclicBarrier可以用于多线程计算数据,最后合并结果的场景;由于CyclicBarrier的计数器可以重置,所以可以使用它处理更为复杂的业务场景,而CountDownLatch计数器只能使用一次。
三、信号量Semaphore
无论是内部锁sychronized还是重入锁ReentrantLock,一次都只允许一个线程访问某一个资源,而信号量却可以指定多个线程同时方位某一个资源;主要用来控制同时访问特定资源的线程数量,它通过协议各个线程,以保证合理的使用公告资源。
import java.util.concurrent.Semaphore; public class SemaphoreTest implements Runnable{ final Semaphore s = new Semaphore(5); @Override public void run() { try { s.acquire(); Thread.sleep(500); System.out.println(Thread.currentThread().getName()+" is Done!"); s.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainTest { public static void main(String[] args) { SemaphoreTest s = new SemaphoreTest(); ExecutorService threadPool = Executors.newFixedThreadPool(20); for(int i = 0; i < 20; i++) { threadPool.submit(s); } threadPool.shutdown(); } }
执行的结果是每五个线程为一组打印消息。
线程池里面有20个可重复使用的线程数量,但是信号量只有5个,也就是每次只能并发5个线程执行,其他线程阻塞。
信号量为5,可以认为线程池里有5把锁,每个线程调用acquire和release分别表示获取锁和释放锁,这样,通过信号量就可以调度多个线程的执行