对 Java 四种线程池的简要分析

1 谈谈线程池

在之前,我们在需要使用线程的时候就去创建一个线程,可谓是非常简便了。但是,当并发的线程数量多了之后,频繁创建线程会令系统的效率大大下降。

那有没有办法可以令我们复用线程?线程在执行完一个任务之后,不会销毁,进而执行其他的任务呢?

事实上,我们可以通过线程池实现相应的功能。在 Java 中,线程池的顶级接口是 Executor,但它并不是线程池的具体实现,真正的线程池实现类为 ThreadPoolExecutor。

我们可以向线程池中传递任务以获得执行,可传递的任务有以下两种,分别是通过 Runnable 实现的任务与通过 Callable 实现的任务,这两者之间的区别为 Runnable 没有返回值但 Callable 有返回值。

2 四种线程池

在 Java 提供了四种线程池的具体实现,分别如下:

  1. newCachedThreadPool:创建一个可缓存线程池
  2. newFixedThreadPool:创建一个定长线程池
  3. newScheduledThreadPool:创建一个定长线程池
  4. newSingleThreadExecutor:创建一个单线程化的线程池

下面对这四种线程池进行简单介绍。

newCachedThreadPool

newCachedThreadPool 是一个可缓存线程池。当调用 execute 方法时,会重用以前构造的线程。该线程池的线程数量并不固定,且线程数量的最大值为 Integer.MAX_VALUE。线程池中的空闲线程有超时限制,这个时间为60秒,超过60秒闲置线程就会被回收。

该线程池适合执行大量耗时较少的任务,当线程池处于闲置状态时,线程池中的所有线程都会因为超时从而被回收。

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {

	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		
		for(int i = 0;i < 10;i++) {
			int index = i;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			cachedThreadPool.execute(() -> System.out.println("第" + index + "个任务,当前执行任务的线程为:" + Thread.currentThread().getName()));
		}
	}
}

结果如下:
在这里插入图片描述
我们可以发现,事实上执行我们任务的都是同一个线程,这是因为当我们执行下一个任务时,上一个任务已经执行完成,可以复用上一个任务使用的线程,不需要新建线程。

newFixedThreadPool

newFixedThreadPool 是一个定长线程池,该线程池可以指定工作线程数量,每当我们提交一个任务时,就会创建一个工作线程,且当工作线程处于空闲状态时并不会被回收。当工作线程的数量超过最大值时,会将提交的任务存放进没有大小限制的队列中。

newFixedThreadPool 只有核心线程,且这些核心线程并不会被回收,故其能够快速响应外界请求。

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {

	public static void main(String[] args) {
		//最大线程个数为3
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
		
		for(int i = 0;i < 10;i++) {
			int index = i;
			fixedThreadPool.execute(() -> {
				System.out.println("第" + index + "个任务,当前执行任务的线程为:" + Thread.currentThread().getName());
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
	}
}

结果如下:
在这里插入图片描述
我们可以发现,由于我们设置的最大线程为3,故当线程池的工作线程等于3个时,即使有新任务进来且无空闲线程,也不会新建线程,而是会将其存放入队列中等待工作线程进行消费。

newScheduledThreadPool

newScheduledThreadPool 是一个定长线程池,它的核心线程数量是固定的,而非核心线程数是没有限制的,且当非核心线程闲置时会被回收。

newScheduledThreadPool 可以延迟运行任务或定时执行任务,适合于执行定时任务或执行周期性任务。

package test;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest {

	public static void main(String[] args) {
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
		System.out.println("开始时间" + System.currentTimeMillis());
		scheduledThreadPool.schedule(() -> System.out.println("执行任务的时间" + System.currentTimeMillis()),5,TimeUnit.SECONDS);
	}
}

结果如下:
在这里插入图片描述
newScheduledThreadPool 确实起到延时执行任务的作用。

newSingleThreadExecutor

newSingleThreadExecutor 是一个单线程化的线程池。该线程池内部只有一个核心线程,任务会存放进一个无界队列中让该线程顺序执行。newSingleThreadExecutor 的特点就是确保所有任务在同一线程内顺序执行。

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {

	public static void main(String[] args) {
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		for(int i = 0;i < 10;i++) {
			int index = i;
			singleThreadExecutor.execute(() -> System.out.println("第" + index + "个任务被执行")); 
		}
	}
}

结果如下:
在这里插入图片描述
可见 newSingleThreadExecutor 保证了上述线程的顺序化执行。

3 线程池的原理

看到这里,可能有的读者会非常疑惑。咦,博主,你之前不是说真正的线程池实现类为 ThreadPoolExecutor 吗,我看你介绍了四种线程池,怎么就没提到 ThreadPoolExecutor 呢?别急,且听我慢慢解释。

我们以 newCachedThreadPool 为例子进行讲解,当你执行下面这行代码时

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

其实真正执行的是 ThreadPoolExecutor 的构造方法,换言之,四种线程池底层都是通过 ThreadPoolExecutor 来进行初始化的。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

其它三种线程池的实现如下:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
	public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor继承了ThreadPoolExecutor且实现了ScheduledExecutorService接口
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

那我们再来看看在 newCachedThreadPool 中调用的 ThreadPoolExecutor 的构造函数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

它真正调用的是这个构造函数

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler) 

每个参数的具体含义如下:

  1. corePoolSize:核心池大小,当线程池的线程数量超过了这个值,会将新的任务放置在等待队列中
  2. maximumPoolSize:线程池最大线程数量,即线程池能创建的最大线程数
  3. keepAlivertime:当活跃线程数大于核心线程数时,多余线程的最大存活时间
  4. unit:存活时间的单位
  5. workQueue:一个存放任务的阻塞队列
  6. threadFactory:线程工厂,用来创建线程
  7. handler:当线程池的任务缓存队列已满并且线程池中的线程数目达到 maximumPoolSize 且仍有任务到来时执行的任务拒绝策略

4 线程池的优点

  1. 线程池可以重用线程,以避免线程的频繁创建和销毁带来的性能开销
  2. 可以有效控制线程池的并发数,有效避免大量的线程争夺 CPU 资源而造成堵塞
  3. 线程池可以对线程进行管理,例如可以提供定时、定期、单线程、并发数控制等功能
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章