目录
CountDownLatch
是java.util.concurrent包中的一个类
让一个或多个线程在运行过程中的某个时间点能停下来等待其他的一些线程完成某些任务后再继续运行。
类似的任务可以使用线程的 join() 方法实现:在等待时间点调用其他线程的 join() 方法,当前线程就会等待join线程执行完之后才继续执行,但 CountDownLatch 实现更加简单,并且比 join 的功能更多。
使用CountDownLatch有三个主要步骤
- 创建CountDownLatch实例时通过构造函数指定计数器的值N;
- CountDownLatch的构造函数接收一个 int 型参数作为计数器,例如想让N任务完成之后才能继续执行,创建CountDownLatch时传入参数N;
- 需要等待的线程调用CountDownLatch实例的await方法,等待计数器的值将为0;
- 被等待的线程在任务完成时调用CountDownLatch实例的countDown方法,计数器的值减1。
例子:有三个线程解析表格中的数据,主线程需要等到表格解析完成后才执行后面的操作
public class CountDownLatchTest {
public static void main(String[] args) throws Exception{
/*创建CountDownLatch实例,计数器的值初始化为3*/
final CountDownLatch downLatch = new CountDownLatch(3);
/*创建三个线程,每个线程等待1s,表示执行比较耗时的任务*/
for(int i = 0;i < 3;i++){
final int num = i;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(String.format("thread %d has finished",num));
/*任务完成后调用CountDownLatch的countDown()方法*/
downLatch.countDown();
}
}).start();
}
/*主线程调用await()方法,等到其他三个线程执行完后才继续执行*/
downLatch.await();
System.out.print("all threads have finished,main thread will continue run");
}
}
Executors
是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。
Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。Executors可以用于方便的创建线程池。
任务的执行:包括Executor框架的核心接口Executors以及其子接口ExecutorService。在Executor框架中有两个关键类ThreadPoolExecutor和ScheduledThreadPoolExecutor实现了ExecutorService接口。
但是在阿里巴巴Java开发手册中也明确指出,『不允许』❌使用Executors创建线程池。
而是通过 ThreadPoolExecutor 。原因如下:
- FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为Integer.MAX.VALUE,可能会创建大量的线程,从而导致OOM。
创建线程方式
newFiexedThreadPool(int Threads)
:
创建固定数目线程的线程池。
newCachedThreadPool()
:
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
newSingleThreadExecutor()
创建一个单线程化的Executor。
newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
Executors.newFixedThreadPool
public static void main(String[] args) {
// 创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
// 创建线程
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
// 将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
// 关闭线程池
pool.shutdown();
}
ThreadLocal
public class ThreadLocalDemo {
public static void main(String[] args) throws InterruptedException {
int nThreads = 10;
final Counter counter = new Counter();
ExecutorService exec = Executors.newFixedThreadPool(nThreads);
final CountDownLatch latch = new CountDownLatch(nThreads);
for(int i = 0; i < nThreads; i++){
exec.submit(new Runnable(){
public void run(){
for(int i = 0; i < 10000; i++){
counter.increase();
}
latch.countDown();
}
});
}
latch.await();
System.out.println("Expected:" + nThreads * 10000 + ",Actual:" + counter.count);
}
static class Counter{
int count = 0;
public void increase(){
this.count++;
}
}
输出:
Expected:100000,Actual:71851
可见最终变量count的状态并不符合预期的逻辑。对于并发问题来说,最简单的解决办法就是加锁,本质是并发访问到串行访问的改变。如下:输出:
static class Counter{
int count = 0;
public synchronized void increase(){
this.count++;
}
}
输出:
Expected:100000,Actual:100000
原因:count变量的值之所以出现不正确的情况,是因为其被多个线程同时访问,而且对某个线程来说,其它线程对变量count的操作结果,该线程是不一定可见的,这是造成count变量最终数据不一致的原因。而用synchronized修饰过后,串行访问时就不存在不可见的情况。从而保证了count变量的正确性。