Java EE——线程池

线程池

池的概念

在之前的章节中我们就提到过池,例如字符串常量池,数据库连接池。
池是为了提升我们代码的效率的。由于有些东西我们频繁的创建销毁过于消耗资源,因此我们用池将暂时用不到的资源存储起来,等到以后有需要了再从池中拿出来

例如我们在招聘时被通知进入了公司的人才池,这并不说明你是个人才,而是说明公司嫌审核简历太麻烦了,现在还用不到你,等公司实在招不到人了才会调用这个人才池

而我们的线程池虽然相对进程来说已经轻量化了,但是多次创建和销毁仍是一种低效的操作,因此有了线程池。在线程使用完成后放到线程池中,等到需要新的线程了再从池子中取出来

为什么从池子中取比创建新的效率高

这是因为我们的计算机由多个部分组成:硬件,驱动,内核,系统调用,应用程序。而应用程序就属于用户态,内核属于内核态。
当我们创建线程时,就需要创建一个PCB,其本质是一个内核中的数据结构,因此我们就需要从用户态切换到内核态来创建
而当我们从线程池中拿一个线程时,这是用户态自己就可以实现的,因此效率和开销更小

Java标准库中的线程池

首先创建一个池对象

ExecutorService pool = Executors.newCachedThreadPool();

我们的这个写法不是构造方法,而是因为构造方法中的参数太多,进行优化后的一种写法——工厂方法

工厂方法

工厂方法是为了弥补构造方法的不足而产生的,由于构造实例时我们可能要传入多个参数,因此就要写多个构造方法,但是这几个构造方法可能参数类型和个数都一样,不构成重载,因此就会出现语法错误。因此我们用新的方法将这些构造方法封装起来,就可以创建不同的对象了

demo

public class Factory {
    static class Point{
        private double r;
        private double a;
        private double x;
        private double y;

        public static Point makePointByXY(double x, double y){
            Point p = new Point();
            p.setX(x);
            p.setY(y);
            return p;
        }

        private void setY(double y) {
            this.y = y;
        }

        private void setX(double x) {
            this.x = x;
        }

        public static Point makePointByRA(double r, double a){
            Point p = new Point();
            p.setR(r);
            p.setA(a);
            return p;
        }

        private void setA(double a) {
            this.a = a;
        }

        private void setR(double r) {
            this.r = r;
        }
    }
}

创建完池任务后就可以加入任务,让线程池中的线程来完成这些任务

pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务");
            }
        });

其他参数

标准库中的ThreadPool有一系列参数,让我们可以构造出不同的线程池

corePoolSize和maximumPoolSize

前者代表核心线程数,也就是线程池中的主力,就算空闲了也留在线程池中,后者是最大线程池,也就是主力加上替补

当我们任务过多时,就会让主力和替补都上
当我们没多少任务,这时就可以开除几个替补了

keepAliveTime和unit

也就是如果我们的替补多长时间不上场,就可以把他开除了,unit是时间的单位

workQueue

我们在创建线程池之前有可能自己就有一个队列,因此我们可以通过这个参数来为线程池传入自己的队列,如果不传入,线程池会自己创建一个队列

threadFactory

这个参数描述了线程如何创建,通过这个参数可以指定线程的创建的方法

RejectedExecutionHandler handler

由于我们线程池中的线程个数有限,超出数量就要阻塞等待,因此如果有过多的任务要完成,就可以通过这个参数来实现拒绝任务的策略

  1. ThreadPoolExecutor.AbortPolicy 代表如果任务太多了线程就崩溃了,神恶魔任务都不干了
  2. ThreadPoolExecutor.CallerRunsPolicy 代表如果任务太多了,就把任务还给给他任务的线程让他自己干
  3. ThreadPoolExecutor.DiscardOldestPolicy 代表如果任务太多了,就把一些最先安排的任务舍弃
  4. ThreadPoolExecutor.DiscardPolicy 代表如果任务太多了,就不干最新安排的任务

demo

创建一个核心线程数为5,最大线程数为10,任务队列为100,3秒的空闲开除替补,拒绝策略为忽略最新任务的线程池

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class demo3 {
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,3,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardOldestPolicy());
    }
}

MyThreadPool

我们要实现一个将用户发给我们的X个任务,分配给我们开发好的Y个线程。要做到当线程不够用时就让任务阻塞等待,当任务没了就让线程池阻塞等待
因此我们可以用阻塞队列那节的消费者生产者模型来解决这个问题

static class MyThreadPool{
        private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
        public void submit(Runnable runnable) throws InterruptedException {
            queue.put(runnable);
        }
        public MyThreadPool(int m){
            for (int i = 0; i < m; i++) {
                Thread t = new Thread(() -> {
                    while(true){
                        Runnable runnable = null;
                        try {
                            runnable = queue.take();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        runnable.run();
                    }
                });
                t.start();
            }
        }
    }

我们的MyThreadPool中有一个阻塞队列参数,
实现了submit方法——将传入的任务放到队列中。
在构造方法中,我们传入了一个数量值,代表要线程池中的线程个数,然后用一个for循环将这些线程创建出来
在每个线程中,都有一个循环的while,使之持续扫描阻塞队列中是否有新的任务需要完成

线程池自定义线程个数

我们自己定义的线程池线程个数究竟多少合适???
这个问题并没有一个确切的答案,因为这和cpu的性能,任务的执行特点都是有关联的

例如如果是CPU密集型任务,也就是有大量的算术运算和逻辑判断的任务,就会大量消耗cpu资源,也就应该少安排一些任务
如果是IO密集型任务,也就是有很多读写任务,那么线程多了也没关系,对cpu的消耗没有那么大

因此,我们应该通过实验的方式来确定多少线程数合适,通过设定不同的数目,测定程序的性能

你可能感兴趣的:(java,java-ee,java,jvm)