一、CountDownLatch:
CountDownLatch允许一个或者多个线程等待其他线程完成操作。
假设现在需要解析一个Excel里的多个sheet数据,此时可以使用多线程。等到所有sheet都解析完成后,需要提示解析完成。最简单的方法是使用join()方法:
public class JoinCountDownLatchTest { public static void main(String[] args) throws InterruptedException { Thread parser1 = new Thread(new Runnable() { @Override public void run() { } }); Thread parser2 = new Thread(new Runnable() { @Override public void run() { } }); parser1.start(); parser2.start(); parser1.join(); parser2.join(); System.out.println("all parser finish!"); } }
join用于让当前线程等待join线程结束。其原理是不停检查join线程是否存活,如果join线程存活,则让当前线程永远等待。
JDK1.5之后的并发包中提供的CountDownLatch也可以实现join的功能,并且比join的功能更多。
public class CountDownLatchTest { static CountDownLatch c = new CountDownLatch(2); //N public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { System.out.println(1); c.countDown(); //countDown()每调用一次,N-1 System.out.println(2); c.countDown(); } }).start(); c.await(); //阻塞当前线程,直到N变为0 System.out.println(3); } }
由于countDown方法可以用在任何地方,所以这里的N个点,可以是N个线程,即一个线程里的N个执行步骤。用在多线程时,只需要把这个CountDownLatch的引用传递到线程里即可。
注意:CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。
二、CyclicBarrier(回环栅栏)
字面意思是可循环使用的屏障。他要做的事情是,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,平藏才会开门,所有被屏障拦截的线程才会继续运行。
import java.util.concurrent.CyclicBarrier; public class CyclicBarrierTest { static CyclicBarrier c = new CyclicBarrier(2); //拦截2个线程 public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { System.out.println("线程1将到达屏障"); c.await(); //提示当前线程到达屏障 System.out.println("线程1继续运行..."); } catch (Exception e) { e.printStackTrace(); } } }).start(); try { System.out.println("主线程将要到达屏障..."); c.await(); System.out.println("主线程继续执行..."); } catch (Exception e) { e.printStackTrace(); } } }
运行结果:
CyclicBarrier还提供了一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction):用于在线程达到屏障时,优先执行barrierAction。
应用场景:可用于多线程的计算数据,最后合并计算结果的场景。
CyclicBarrier和CountDownLatch的区别:CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,所以CyclicBarrier能处理更为复杂的业务场景,例如计算发生错误,可以重置计数器。
三、Semaphore(信号量)
是用来控制同时访问特定资源的线程数量。例如XX马路要限制流量,只允许同时有100辆车在这条路上行驶,其他的必须在路口等待。当100辆中有5辆驶出这条马路,就可以让另外5辆驶入这条马路。
应用场景:Semaphore可以做流量控制,特别是公用资源有限的应用场景,比如数据库连接。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreTest { private static final int THREAD_COUNT = 30; private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); private static Semaphore s = new Semaphore(10); //最大流量为10 public static void main(String[] args) { for(int i=0;i<THREAD_COUNT;i++){ threadPool.execute(new Runnable() { @Override public void run() { try { s.acquire(); //获取一个许可证 System.out.println("save data ok!"); s.release(); //归还许可证 } catch (InterruptedException e) { e.printStackTrace(); } } }); } threadPool.shutdown(); } }代码中,虽然有30个线程在执行,但是只允许10个并发执行。