Java线程池

线程池

1. 线程池的主要处理流程

在这里插入图片描述

  1. 提交一个新任务到线程池时,首先判断核心线程数是否都在执行任务:
    • 如果不是,那么需要创建一个线程来执行新提交的任务;
    • 如果所有的核心线程都在执行任务,那么 进入下一步。
  2. 判断线程池中的工作队列是否已满。如果工作队列还没有满,那么新提交的任务就进入工作线程进行等待;如果工作队列已经满了,那进入下一步;
  3. 判断线程池中所有的线程是否都处于工作状态,如果没有,那么就创建一个线程来处理任务;如果线程所有的线程都已经处于工作状态,那么就按照拒绝策略来处理该任务。

2. 创建线程的方法

  1. 继承Thread类

    • 创建一个继承于Thread类的子类
    • 重写Thread类的run()
    • 将此线程执行的操作声明在run()中
    • 创建Thread类的子类的对象 / 实例
    • 通过此对象调用start()方法
    public class CreateThreadDemo1 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.print(i * i + " ");
            }
        }
    
        public static void main(String[] args) {
            CreateThreadDemo1 ct1 = new CreateThreadDemo1();
            ct1.start();
        }
    }
    
  2. 实现Runnable接口

    • 创建一个实现了Runnable接口的类
    • 实现类去实现Runnable中的抽象方法:run()
    • 创建实现类的对象
    • 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    • 通过Thread类的对象调用start()
    public class CreateThreadDemo2 implements Runnable{
        @Override
        public void run() {
            System.out.println("创建线程方法二");
        }
    
        public static void main(String[] args) {
            CreateThreadDemo2 ct2 = new CreateThreadDemo2();
            Thread thread = new Thread(ct2);
            thread.start();
        }
    }
    
  3. 实现Callable接口

    • 创建一个实现Callable的实现类
    • 实现call方法,将此线程需要执行的操作声明在call()中
    • 创建Callable接口实现类的对象
    • 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
    • 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
    • 获取Callable中call方法的返回值 (boolean)
    public class CreateThreadDemo3 implements Callable {
        @Override
        public Object call() throws Exception {
            System.out.println("创建线程方法三");
            return false;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            CreateThreadDemo3 ct3 = new CreateThreadDemo3();
            FutureTask<Boolean> task = new FutureTask<Boolean>(ct3);
            Thread thread = new Thread(task);
            thread.start();
            System.out.println(task.get());  //false,获取call方法中的返回值
        }
    }
    
  4. 使用线程池的方式

    • 使用Executors方式创建,不推荐,具体原因如下图所示
    • 通过ThreadPoolExecutor方式创建

在这里插入图片描述

Integer.MAX_VALUE = 2147483647

Integer.MIN_VALUE = -2147483648

3. 线程池主要解决两个问题

  1. **当执行大量异步任务时线程池能够提供较好的性能。**在不使用线程池时,每当需要执行异步任务时,都直接new一个线程来运行,而线程的创建和销毁都是需要开销的。线程池里面的线程可以复用,不需要每次执行异步任务时新建和销毁。
  2. 线程池提供了一种资源限制和管理的手段,比如限制线程的个数,动态增加线程等。

优点:

  • 方便管理;
  • 提高响应的速度
  • 降低资源的消耗; 可以复用

4. 三大方法

Executors.newSingleThreadExecutor();  //单个线程
Executors.newFixedThreadPool(10);//创建一个固定大小的线程池
Executors.newCachedThreadPool();  //可伸缩的线程池

关闭线程池:使用shutdown()函数关闭。

上面三条语句对应的源码:本质是new一个ThreadPoolExecutor对象

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

5. 七大参数

ThreadPoolExecutor的底层源码

public ThreadPoolExecutor(int corePoolSize, //核心线程数
                          int maximumPoolSize, //最大线程数
                          long keepAliveTime, //线程存活时间
                          //超时单位
                          TimeUnit unit,
                          //阻塞队列
                          BlockingQueue<Runnable> workQueue,
                          // 线程工厂
                          ThreadFactory threadFactory,
                          // 拒绝策略
                          RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:线程池的核心线程数,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。说白了就是,即是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。

maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize。

keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。

unit超时单位:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。

workQueue:用于保存等待执行任务的阻塞队列,提交的任务将会被放到这个队列里。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工
    厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

threadFactory:线程工厂,用来创建线程。主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。

handler:拒绝策略即当线程和队列都已经满了的时候,应该采取什么样的策略来处理新提交的任务。默认策略是AbortPolicy(抛出异常),其他的策略还有:CallerRunsPolicy(只用调用者所在线程来运行任务)、DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务)、DiscardPolicy(不处理,丢弃掉)

自定义一个线程池,使ThreadPoolExecutor,最大线程应该如何定义???

分为两种:

  • 1)CPU密集型:cpu有多少个核,就可以有多少个线程同时执行。这里可以将最大线程数设置和cpu的核数相同,保证执行效率最高。

    // 获取CPU的核数(即任务管理器-->性能 --> CPU --> 逻辑处理器的大小)
    Runtime.getRuntime().availableProcessors()
    
  • 2)IO密集型:判断程序中十分耗IO的线程,只要在这个基础上比耗IO线程数大就行。可以设置为 2倍。

6. 四种拒绝策略

  • AbortPolicy(抛出异常,默认):

  • CallerRunsPolicy(只用调用者所在线程来运行任务):哪儿来的回哪。Main线程执行。

  • DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务):队列满了,不会抛出异常。尝试与第一个竞争,如果竞争失败,那么仍然会被抛弃。

  • DiscardPolicy:队列满了,不处理,直接丢弃掉

7. 向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。

  • execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
  • submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

8. 线程池都有哪些状态?

有如下五个状态:

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BTS;
  • Running: 一旦被创建,就处于此状态,可以接受新任务以及对已经添加的任务进行处理.
  • Shutdown: 此时不接收新任务,但是可以处理已添加的任务。在此状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 Shutdown-> Tidying。
  • Stop:此状态不接收新任务,不处理已添加任务,并且会中断正在处理的任务。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> Tidying状态。
  • Tidying:当所有的任务已终止,ctl记录的任务数变为0,线程池会变成tidying状态,当线程池变为TIDYING状态会执行terminated()方法。
  • Terminated:线程池彻底终止会变成这个状态。当在TIDYING状态,执行完terminated()方法后,就会由TIDYING状态变为TERMINATED状态。
    在这里插入图片描述

9. 关闭线程池

两种方法可以关闭线程池:

  • shutdown()

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    
  • shutdownNow()

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
    

原理:

遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。

区别:

  • shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表;
  • shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
  • 通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值