1 谈谈线程池
在之前,我们在需要使用线程的时候就去创建一个线程,可谓是非常简便了。但是,当并发的线程数量多了之后,频繁创建线程会令系统的效率大大下降。
那有没有办法可以令我们复用线程?线程在执行完一个任务之后,不会销毁,进而执行其他的任务呢?
事实上,我们可以通过线程池实现相应的功能。在 Java 中,线程池的顶级接口是 Executor,但它并不是线程池的具体实现,真正的线程池实现类为 ThreadPoolExecutor。
我们可以向线程池中传递任务以获得执行,可传递的任务有以下两种,分别是通过 Runnable 实现的任务与通过 Callable 实现的任务,这两者之间的区别为 Runnable 没有返回值但 Callable 有返回值。
2 四种线程池
在 Java 提供了四种线程池的具体实现,分别如下:
newCachedThreadPool:创建一个可缓存线程池
newFixedThreadPool:创建一个定长线程池
newScheduledThreadPool:创建一个定长线程池
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)
每个参数的具体含义如下:
corePoolSize:核心池大小,当线程池的线程数量超过了这个值,会将新的任务放置在等待队列中
maximumPoolSize:线程池最大线程数量,即线程池能创建的最大线程数
keepAlivertime:当活跃线程数大于核心线程数时,多余线程的最大存活时间
unit:存活时间的单位
workQueue:一个存放任务的阻塞队列
threadFactory:线程工厂,用来创建线程
handler:当线程池的任务缓存队列已满并且线程池中的线程数目达到 maximumPoolSize 且仍有任务到来时执行的任务拒绝策略
4 线程池的优点
线程池可以重用线程,以避免线程的频繁创建和销毁带来的性能开销
可以有效控制线程池的并发数,有效避免大量的线程争夺 CPU 资源而造成堵塞
线程池可以对线程进行管理,例如可以提供定时、定期、单线程、并发数控制等功能
————————————————
原文链接:「Geffin」https://blog.csdn.net/Geffin/article/details/105231333
|
|