(一)线程池概念
我们知道,多线程是针对多任务处理的,可以多线程可以并发执行多个任务,提高了程序执行效率。但是线程的创建和销毁需要时间,如果频繁创建和销毁线程,可能会影响执行效率,线程池可以解决这个问题。顾名思义,线程池可以理解为一个预先创建的线程集合,每次有任务来的时候,从线程池中取得一个线程去执行任务(如果线程池无空闲线程,可能要新建线程或者等待),执行完毕后线程暂不销毁,等待执行下一个任务,这样节省了任务执行时创建和销毁线程的时间,因此提高了执行效率。
(二)线程池的优势
(1)减少了频繁创建和销毁线程所用的时间,提高了执行效率。
(2)可以用于控制线程数量,避免线程过多,占用过多系统资源,导致任务阻塞。
(3)可以管理线程,控制线程延迟执行时间(跟线程池的任务队列有关)。
(三)应用场景
对于线程的创建和销毁的时间远大于线程执行时间的场合适用,如果需要控制线程执行时间,也可以考虑适用线程池。
(四)线程池相关知识
1.线程池(ThreadPool)涉及的几个常见接口和类
(1)Executor接口:是线程池的基本接口,接口中的execute(Runnable task)用于执行,用于执行实现了Runnable接口的类的对象表示的任务;
(2)ExecutorService接口:该接口是Executor的子接口,接口中包含一些管理线程池的方法,如shutdown(),isTerminated()等
(3)ThreadPoolExecutor类:该类继承AbstractExecutorService类,实现了Executor接口,通过ThreadPoolExecutor类的有参构造函数,可以创建一个线程池。hreadPoolExecutor类的构造函数,稍后说明。
(4)Executors类:该类提供了几个静态方法,用于创建一些常用类型线程池,能够使用该类创建线程池就尽量使用该类,以免使用ThreadPoolExecutor类的构造方法创建线程池时可能的错误。
2. ThreadPoolExecutor类的构造函数
//构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
该类的构造函数对于线程池的模式由比较大的影响,这里对参数予以说明,首先由以下5个基本参数:
- corePoolSize,核心线程数,对于线程池,当线程池中的线程数量小于核心线程时,如果有任务到达,一般是直接创建一个新的线程执行任务,如果线程数达到核心线程数,则新来的任务加入任务队列;
- maximumPoolSize,线程池的最大线程数;
- ,keepAliveTime ,非核心线程的超时时间,如果非核心线程的空闲时间超过该时间,则销毁该线程直到没有非核心线程,核心线程默认无超时时间,但是如果设置了allowCoreThreadTimeOut(true),即允许核心线程超时,那么核心线程空闲超过超时时间后也会被销毁,知道线程池中的线程数为0;
- ,TimeUnit unit,时间单位,可以是从天道纳秒。
- BlockingQueue<Runnable> workQueue,任务执行队列,任务执行队列是一个很重要的参数,对任务执行方式影响较大。有4中任务执行队列:
a)SynchronousQueue,使用该队列时,任务直接提交线程执行,队列中不保留任务,如果没有空闲线程,则直接创建一个新线程,为了避免线程数超过最大线程数,需要将最大线程数设置为很大(Integer.MAX_VALUE);
b) LinkedBlockingQueue,使用该队列时线程数不会超过核心线程数,当线程数量为核心线程数切没有空闲线程时,任务会加入等待队列,等待队列没有队列大小限制。
c) ArrayBlockingQueue,该队列有大小限制,如果队列已经满了,如果此时线程数量小于最大线程数量,则新建线程,如果超过最大线程数量,则执行拒绝策略;
d)DelayedWorkQueue,无界阻塞队列,可以控制任务延时执行。
- threadFactory 线程工场
- RejectedExecutionHandler handler 拒绝策略,如下
ThreadPoolExecutor.AbortPolicy : 丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy :丢弃任务但不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy :丢弃队列最前面的任务,执行新提交的任务
ThreadPoolExecutor.CallerRunsPolicy :调用线程处理该任务
3. 线程池任务执行策略
- 如果线程数小于核心线程数,则新建线程执行任务
- 如果线程数大于核心线程数小于最大线程数,将任务放到任务队列,等待有空闲线程取出执行;如果加入队列失败比如队列已满,则新建线程执行任务
- 如果线程数超过了最大线程数,则执行拒绝策略。
4. Executors类定义的四种线程池:
- SingleThreadPool:单个线程的线程池,核心线程数和最大线程数为1,只有一个线程,如果没有空线线程,任务等待。工作队列使用LinkedBlockingQueue
- FixedThreadPool:固定个数线程池,核心线程数和最大线程数都为n,如果线程数量没达到核心线程数,创建线程执行任务,如果线程数达到核心线程数,加入队列等待空闲线程执行。工作队列使用LinkedBlockingQueue。
- CachedThreadPool:可缓冲线城池,核心线程数0,最大线程数为最大整数值(无限大),没有线程数限制,没来一个任务立即提交线程执行,如果有空闲线程使用空闲线程,没有空闲线程直接新建一个线程,当线程空闲时间超过60s被回收。工作队列使用SynchronousQueue。
- ScheduledThreadPool,可定时执行任务的线程池,核心线程数固定,非核心线程数没有限制,非核心线程超时回收。任务到了延时时间后执行任务。工作队列使用无界的DelayedWorkQueue。
这四种线程池的静态创建方法如下:
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 ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,0,TimeUnit.NANOSECONDS,new DelayedWorkQueue());
}
这四种线程池可使用Executors的静态方法创建,例如创建一个固定大小的线程池:
ExecutorService pool = Executors.newFixedThreadPool(3);
5.提交任务给线程池
可以使用execute(Runnable task)方法,提交任务。
五、测试实例,
以多个客户端和一个服务端的socket通信为例,服务端启动时创建一个固定大小的线程池。服务端每接收到一个连接请求后(通信任务),交给线程池执行,表示任务的类实现了Runnable接口,用于跟客户端进行读写操作,该类的对象作为任务通过execute(Runnable task)提交给线程池:
(1)服务端程序 SocketSverver.java
import java.net.* ;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.* ;
public class SocketServer {
private ExecutorService pool=null;
public static void main(String[] args) {
int port = 46322;
SocketServer server = new SocketServer();
server.serverTest(port);
}
private class InnerThread implements Runnable{
private Socket clientSocket = null;
public InnerThread(Socket clientSocket) {
this.clientSocket = clientSocket;
}
public void run() {
//accept connection and communicate
String buf = null;
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
pw = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
System.out.println("client ip and port: " + clientSocket.getInetAddress().toString() + ":"+clientSocket.getPort());
System.out.println("ready");
//read
while(true){
buf = br.readLine();
System.out.println(Thread.currentThread().getName()+" client: " + buf);
if(buf.equals("close")){
break;
}
//write
System.out.println(Thread.currentThread().getName()+" server: respose");
pw.println("response");
pw.flush();
}
//close
System.out.println("close client");
br.close();
pw.close();
clientSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void serverTest (int port) {
ServerSocket serverSocket = null;
pool = Executors.newFixedThreadPool(2);
try{
//bind and listen port
serverSocket = new ServerSocket(port);
System.out.println("bind port: " + port);
while(true){
//accept
Socket clientSocket = null;
//System.out.println("waiting connection...");
clientSocket = serverSocket.accept();
InnerThread innerThread = new InnerThread(clientSocket);
pool.execute(innerThread);
}
}catch(Exception e){
e.printStackTrace();
pool.shutdown();
}
}
}
2. 客户端程序SocketClient.java:
import java.net.* ;
import java.io.* ;
import java.util.Scanner;
public class SocketClient {
public static void main(String[] args) {
int port = 46322;
String ip = new String("127.0.0.1");
SocketClient client = new SocketClient();
client.clientTest(ip, port);
}
public void clientTest(String ip, int port){
Socket clientSocket = null;
BufferedReader br = null;
PrintWriter pw = null;
Scanner sc = new Scanner(System.in);
String buf = null;
//connect and communicate
try {
//connect
clientSocket = new Socket(ip, port);
br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
pw = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
System.out.println("try to connect server "+ ip + ": " + port);
System.out.println("ready");
//write and read
while(true){
System.out.print("client: ");
buf = sc.nextLine();
pw.println(buf);
pw.flush();
if(buf.equals("close")){
break;
}
buf= br.readLine();
System.out.println("server: " + buf);
if(buf.equals("close")){
break;
}
}
//close
br.close();
pw.close();
sc.close();
clientSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里读写socket使用的是BufferedReader和PrintWriter,注意器实例化的方式:
BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
编译客户端和服务端程序,先启动服务端,然后启动两个客户端,客户端发送数据给服务端,服务端收到后回一个"response" ,客户端输入close则关闭该链接以及客户端,服务如果要停止可以ctrl+c,结果如下: