java线程池实例及总结(以多路socket通信为例)

(一)线程池概念

我们知道,多线程是针对多任务处理的,可以多线程可以并发执行多个任务,提高了程序执行效率。但是线程的创建和销毁需要时间,如果频繁创建和销毁线程,可能会影响执行效率,线程池可以解决这个问题。顾名思义,线程池可以理解为一个预先创建的线程集合,每次有任务来的时候,从线程池中取得一个线程去执行任务(如果线程池无空闲线程,可能要新建线程或者等待),执行完毕后线程暂不销毁,等待执行下一个任务,这样节省了任务执行时创建和销毁线程的时间,因此提高了执行效率。

(二)线程池的优势

(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,结果如下:

阅读更多
个人分类: java 多线程
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭