一、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个并发执行。