小编最近在公司的项目中处理订单的时候用到了多线程技术,但是随着使用的深入发现了一些线程的使用情况的限定,继而搜索出了线程池这个东西,所以现在和大家分享一下使用心得。
一、为什么要使用线程池
在我们的多线程应用中,需要CPU对于多线程的支持,现在的主流的CPU已经对于多线程的程序有了很好的支持了,并且也鼓励多线程编程的实现进一步活用化,然而使用多线程面临的就是对于共享资源的争夺还有对于硬件CPU资源的等候。我们使用多线程,无非是想要加快程序的运行效率,但是创建线程,销毁线程又是很费资源和时间的事情,如果是手动创建线程的话,使用不当,反而会造成CPU资源阻塞等一系列情况。线程池就是用来解决这个事情的。
一个线程的声明周期为以下三步:T1创建线程,T2在线程中执行任务,T3销毁线程。当我们的一个线程的执行时间T1+T3 > T2的时候,我们就可以使用线程池这个技术来提高服务器的利用率(使用性能)了。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
二、线程池的组成
一个线程池大体上由四个部分组成:线程池管理器(ThreadPool)、工作线程(PoolWorker)、任务接口(Task)、任务队列(taskQueue)。
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。
三、java类库中提供的线程池
以上为JDK中的线程池的接口以及实现,下面着重介绍通过这个接口实现的几种线程池:
(1)、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
(2)、newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
(3)、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
}
表示延迟3秒执行
(4)、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
四、总结
在项目实战中,我们使用的是带有总数控制的newFixedThreadPool,这个线程池,他可以控制并发线程的总数,对于数以万计的订单来说,着实是一个不错的选择,之前没有用线程池,导致数据库服务器CPU占用长时间保持在100%状态,并且没有很好的控制锁的数量,导致了大量的线程在内存中处于休眠的状态,整个服务器运行十分缓慢,用了线程池之后可以方便的控制线程数量,可以配合服务器的性能进行动态调整。