JAVA基础知识复习之线程池

1、为什么要用线程池?
诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式到达服务器,这种方 式可能是通过网络协议(例如 HTTP、FTP 或 POP)、通过 JMS 队列或者可能通过轮询数据库。不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。

构建服务器应用程序的一个过于简单的模型应该是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。实际上,对于原型开发这种方法工作得很 好,但如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。每个请求对应一个线程(thread-per-request)方 法的不足之一是:为每个请求创建一个新线程的开销很大;为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用 户请求的时间和资源更多。

除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。在一个 JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时 线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也 就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

2,java 一共含有四种线程池: newCachedThreadPool, newFixedThreadPool, newSingleThreadExecutor, newScheduledThreadPool。

newCachedThreadPool: 顾名思义是一种可缓存的线程池, 线程池除了维护初始大小的线程外,当任务数量超出线程池大小时,便会新建线程, 而且当线程完成任务之后不会马上销毁,而是会保留一段时间(默认60s),这种极大的减少了线程创建和销毁的资源消耗, 当这种线程池的弊端是 线程最大值过大, 如果用于高并发且任务较长场景,很容易将内存全部吃光。 所以使用于执行短期异步小任务,或者并发量不高的场景。

newFixedThreadPool : 是基于无界队列的线程池, 维护的线程数量是固定的,如果线程均在繁忙状态,则新任务会放入无界队列里。 适用于长期任务。 如果用于短期任务,任务数量 < 线程池数量时,性能不会跟newCachedThreadPool 太大的区别,但是超出时, 因为是短期的, 所以任务会不断的被放入队列,又被取出, 时间间隔很短,并且过多的短期任务放入队列中,回使得内存吃紧,当任务数量过多时会造成很大的资源浪费。

newSingleThreadExecutor: 顾名思义是只有一个单线程的线程池,具体场景不太清晰, 按照线程含义来看,适用于需要按顺序(FIFO, LIFO, 优先级)的执行任务的场景,以及thread confinement(变量只能由特定线程访问)的要求。

newScheduleThreadPool : 如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构。这个线程的应用场景就很容易知道了, 需要周期性执行的任务使用该线程池。
3.ThreadPoolExecutor构造方法
Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:

// Java线程池的完整构造函数
public ThreadPoolExecutor(
  int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
  int maximumPoolSize, // 线程数的上限
  long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,
                                     // 超过这个时间,多余的线程会被回收。
  BlockingQueue<Runnable> workQueue, // 任务的排队队列
  ThreadFactory threadFactory, // 新线程的产生方式
  RejectedExecutionHandler handler) // 拒绝策略

竟然有7个参数,很无奈,构造一个线程池确实需要这么多参数。这些参数中,比较容易引起问题的有corePoolSize, maximumPoolSize, workQueue以及handler:

corePoolSize和maximumPoolSize设置不当会影响效率,甚至耗尽线程;
workQueue设置不当容易导致OOM;
handler设置不当会导致提交任务时抛出异常。
corePoolSize: 线程池的核心线程数目,当一个请求进来时如果当前线程池中线程数量小于这个值,则直接通过ThreadFactory新建一个线程来处理这个请求,如果已有线程数量大于等于这个值则将请求放入阻塞队列中。
maximumPoolSize: 线程池的最大线程数目,当线程池数量已经等于corePoolSize并且阻塞队列也已经满了,则看线程数量是否小于maximumPoolSize:如果小于则创建一个线程来处理请求,否则使用“饱和策略”来拒绝这个请求。对于大于corePoolSize部分的线程,称作这部分线程为“idle threads”,这部分线程会有一个最大空闲时间,如果超过这个空闲时间还没有任务进来则将这些空闲线程回收。
keepAliveTime和unit: 这两个参数主要用来控制idle threads的最大空闲时间,超过这个空闲时间空闲线程将被回收。这里有一点需要注意,ThreadPoolExecutor中有一个属性:private volatile boolean allowCoreThreadTimeOut;,这个用来指定是否允许核心线程空闲超时回收,默认为false,即不允许核心线程超时回收,核心线程将一直等待新任务。如果设置这个参数为true,核心线程空闲超时后也可以被回收。
workQueue: 阻塞队列,超过corePoolSize部分的请求放入这个阻塞队列中等待执行。阻塞队列分为有界阻塞队列和无界阻塞队列。在创建阻塞队列时如果我们指定了这个队列的“capacity”则这个队列就是有界的,否则是无界的。这里有一点需要注意:使用线程池之前请明确是否真的需要无界阻塞队列,如果阻塞队列是无界的,会导致大量的请求堆积,进而造成内存溢出系统崩溃。
threadFactory: 是一个线程池工厂,主要用来为线程池创建线程,我们可以定制一个ThreadFactory来达到统一命名我们线程池中的线程的目的。
handler: 饱和策略,用来拒绝多余的请求。饱和策略有:CallerRunsPolicy:请求脱离线程池运行(调用者caller线程来运行这个任务);AbortPolicy:抛出RejectedExecutionException异常;DiscardPolicy:丢弃这个任务,即什么也不做;DiscardOldestPolicy:将阻塞队列中等待时间最久的任务删除(即队列头部的任务),将新的任务加入队尾。
在这里插入图片描述
3,测试实例
以多个客户端和一个服务端的socket通信为例,服务端启动时创建一个固定大小的线程池。服务端每接收到一个连接请求后(通信任务),交给线程池执行,表示任务的类实现了Runnable接口,用于跟客户端进行读写操作,该类的对象作为任务通过execute(Runnable task)提交给线程池:
(客户端输入分数,服务端显示是否及格)
(1)服务端程序 SocketSverver.java

扫描二维码关注公众号,回复: 8631534 查看本文章
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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();
                   int num= Integer.parseInt(buf);
                    System.out.println(Thread.currentThread().getName()+" client: " + buf);

                    if(buf.equals("close")){
                        break;
                    }
                   if(num<60){
                       System.out.println(Thread.currentThread().getName()+":不及格");
                       pw.println("response:"+Thread.currentThread().getName()+":不及格");
                   }else {
                       System.out.println(Thread.currentThread().getName()+":及格");
                       pw.println("response:"+Thread.currentThread().getName()+":及格");
                   }

                    //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();
        }

    }
}
  1. 客户端程序SocketClient.java:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
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();
        }

    }

}
发布了146 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43689040/article/details/102174418