1.1 线程池思想概述
池化技术
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。
1.2 线程池概念
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。因为启动线程的时候会在内存中开辟一块空间,消耗系统资源,同时销毁线程的时候首先要把和线程相关东西进行销毁,还要把系统的资源还给系统。这些操作都会降低操作性能。尤其针对一个线程用完就销毁的更加降低效率。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,生存期较短的线程指的是用完一次线程就丢掉。更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
线程池工作原理如下图所示:
需求:我有一段任务,需要执行100次。
说明:
每次线程执行完任务以后,线程不会销毁,会放回线程池中,每次在执行任务的时候又会到线程池中去取线程。这样会提高效率。
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
1.3 线程池的使用
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
如何获取线程池?
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此使用java.util.concurrent.Executors
线程池工具类来生成一些常用的线程池。官方建议使用Executors工具类来创建线程池对象。
Executors是一个线程池的工具类,专门用来生产线程池。
线程池Executors的工具类中的函数如下所示,通过以下函数可以创建线程池:
public static ExecutorService newFixedThreadPool(int nThreads)
:创建一个可重用固定线程数的线程池。
说明:以上这个函数是按照指定线程个数来创建线程池,在创建线程池的时候直接指定线程的个数。
问题:我们发现上述获取线程池之后,函数的返回值是ExecutorService ,既然是获取线程池,那么为什么返回的不是Executor线程池却是一个接口ExecutorService 呢?
进一步解释上述获取线程池的函数返回值类型ExecutorService :
ExecutorService 属于真正的线程池接口。
public interface ExecutorService extends Executor
ExecutorService 接口的父接口是Executor。
如何执行我们的线程任务呢?
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
-
void execute(Runnable command) 在将来的某个时间执行给定的命令。
说明:这个方法是ExecutorService 的父类Executor中的方法。表示获取线程池中的某一个线程对象,并执行。
使用线程池中线程对象的步骤:
A:自定义一个类,作为任务类并实现Runnable接口;
B:实现Runnable接口中的run方法;
C:创建任务类的对象;
D:获取线程池对象;
E:直接执行任务;
用线程池执行卖票程序
需求:使用线程池来完成卖票任务。
Runnable实现类代码:
package com.SiyualChen.day03.test06;
public class SellTicketTask implements Runnable{
private int tickets=100;
@Override
public void run() {
while (true) {
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出票" + "这是" + (100 - tickets + 1) + "第张票");
tickets--;
} else {
break;
}
}
}
}
}
package com.SiyualChen.day03.test06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SellTickestDemo {
public static void main(String[] args) {
SellTicketTask sellTicketTask = new SellTicketTask();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(sellTicketTask);
}
}
}
pool-1-thread-4出票这是90第张票
pool-1-thread-4出票这是91第张票
pool-1-thread-3出票这是92第张票
pool-1-thread-3出票这是93第张票
pool-1-thread-3出票这是94第张票
pool-1-thread-3出票这是95第张票
pool-1-thread-3出票这是96第张票
pool-1-thread-3出票这是97第张票
pool-1-thread-3出票这是98第张票
pool-1-thread-3出票这是99第张票
pool-1-thread-3出票这是100第张票
大家以为这样就结束啦? 有没有发现一个问题,线程是跑完了,程序没关闭。我类个去!!!这里有个坑。必须调用shutdown()函数结束线程。
shutdown()。 //结束线程
package com.SiyualChen.day03.test06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SellTickestDemo {
public static void main(String[] args) {
SellTicketTask sellTicketTask = new SellTicketTask();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(sellTicketTask);
}
executorService.shutdown(); //这里是坑
}
}