线程管理

2022年05月15日 阅读数:4
这篇文章主要向大家介绍线程管理,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

线程组

线程组(ThreadGroup)能够用来表示一系列类似或者相关的线程集合。java

一个线程组能够包含多个线程和线程组,一个线程组包含另外的线程组那么这个线程组被称为另外这些线程组的父线程组,如过一个线程没有被关联线程组,那么这个线程就属于其父线程所属的线程组,java虚拟机在建立main线程的时候会为其指定一个线程组,因此java中任何一个线程都有与之关联的线程组,咱们为您能够经过Thread.getThreadGroup()来获取当前线程所关联的线程组。安全

线程的未捕获异常与监控

当线程在run的时候出现未捕获的异常,那么致使run方法退出,且相应的线程也将会终止。对于这种状况咱们能够在线程启动以前为其关联一个UncaughtExceptionHandler,当线程出现未捕获异常抛出的时候, 线程会在退出以前调用UncaughtExceptionHandler.UncaughtException(Thread t, Throwable e)方法,咱们能够在这个方法中进行相应的日志处理,或者从新建立一个线程来替代这个线程。并发

public class UncaughtExceptionDemo {
    static class MyExceptionHandler implements Thread.UncaughtExceptionHandler{

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println(t.getName()+"线程抛出了异常");
            MyThread thread = new MyThread();
            thread.setName("B");
            thread.setUncaughtExceptionHandler(new MyExceptionHandler());
            thread.start();
        }
    }

    static class MyThread extends Thread{
        @Override
        public void run() {
            if ("A".equals(Thread.currentThread().getName())){
                throw new BusinessException("出现异常:"+Thread.currentThread().getName());
            }
            System.out.println(Thread.currentThread().getName());
        }
    }

    static class BusinessException extends RuntimeException{
        public BusinessException(String message) {
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("A");
        myThread.setUncaughtExceptionHandler(new MyExceptionHandler());
        myThread.start();
    }
}
===========结果============
出现异常:A
A线程抛出了异常
B

线程池

使用线程池的好处ide

  • 下降资源消耗:线程池中存有能够复用的线程,能够减小线程 建立-销毁 所带来的性能消耗
  • 提升线程响应速度:线程池中的的线程直接响应任务,而不是响应任务后还要建立线程
  • 有利于线程的管理

线程池中各个参数的含义性能

线程池中各个组件的职责及流程this

  1. 线程池管理器,负责线程池的建立、销毁、添加任务等
  2. 工做线程
  3. 任务队列,用来临时存储任务,能够起到缓冲的做用,由于通常处于并发场景,因此任务队列通常采用BlockingQueue来保障线程安全。
  4. 任务,任务要求实现统一接口,以便工做线程能够执行和处理

什么是拒绝策略spa

  1. 线程池调用shutdown后,线程池会等待当前线程池中的全部线程执行完,但不会接收新的任务。
  2. 核心线程池满了 - 任务队列满了 - 线程池达到了最大线程数 - 线程池不会接收新的任务。

常见的几种拒绝策略操作系统

  1. DiscardPolicy:会直接忽视新加进来的任务
  2. DiscardOldestPolicy:会移除队列中的队头任务,将新的任务插到队尾
  3. AbortPolicy:会抛出一个名为RejectedExecutionException的运行时异常,能够在catch中从新处理这个任务
  4. CallerPolicy:有新任务可是线程池没有能力处理时,由提交任务的线程执行。(最完善)

常见线程池线程

  1. FixedThreadPool3d

    固定线程数的线程池,核心线程数和最大线程数相同new FixedThreadPool(10) - 最开始线程数从0开始增长当增长到10后就再也不增长,有新的任务就放到任务队列当中,当再有新的任务就不会有新的线程产生。

  2. CachedThreadPool

    线程池中的线程能够无限增长,但不会超过 Integr.MAX\_VALUE(2 ^ 31,几乎不会达到)

  3. ScheduleThreadPool

    周期性执行任务

  4. SingleThreadExecutor

    只有一个线程来执行任务,可是当前线程出现异常时,线程池会新建立一个线程执行后续任务,好处就是执行的任务是有序的。

  5. SingleThreadScheduleExecutor

    与3及其类似是ScheduleThreadPool的一个特例, 至关于 new ScheduledThreadPoolExecutor(1)

  6. ForkJoinPool

    经常使用于递归场景,例如树遍历

    RecursiveTask 类是对ForkJoinTask 的一个简单的包装,这时咱们重写 compute() 方法,当 n<=1时直接返回,当 n>1 就建立递归任务,也就是 f1 和 f2,而后咱们用 fork() 方法分裂任务并分别执行最后在 return 的时候,使用 join() 方法把结果汇总,这样就实现了任务的分裂和汇总。

    class Fibonacci extends RecursiveTask<Integer> { 
        int n;
    
        public Fibonacci(int n) { 
            this.n = n;
        } 
    
        @Override
        public Integer compute() { 
            if (n <= 1) { 
                return n;
            } 
            Fibonacci f1 = new Fibonacci(n - 1);
            f1.fork();
            Fibonacci f2 = new Fibonacci(n - 2);
            f2.fork();
            return f1.join() + f2.join();
        } 
     }
     
    public static void main(String[] args){ 
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        for (int i = 0; i < 10; i++) { 
            ForkJoinTask task = forkJoinPool.submit(new Fibonacci(i));
            System.out.println(task.get());
        } 
     }

为何不建议使用Executors建立线程池

下图是各个线程池对应使用的阻塞队列

  1. FixedThreadPool和SingleThreadExecutor

    由于LinkBlockingQueue容量几乎是无限大的,若是任务的消费速度远超过任务生产速度会致使队列中堆积大量的任务有可能会致使内存溢出。

  2. CatchedThreadPool

    当任务较多的时候,线程池会无限制的新建线程,最终可能致使操做系统没法新建线程或者是内存溢出

  3. ScheduleThreadPool和SingleThreadScheduleExecutor

    DelayedWorkQueue也是一个无界队列,若是队列中存在大量的任务一样可能内存溢出。

如何正确关闭线程池

  1. shutdown

    调用这个方法后,线程池不会当即中止而是等队列中的全部任务处理完毕后才会完全关闭,若是此时还有新的任务提交将会被拒绝。

  2. shutdownNow

    当即终止线程池中的全部操做,不安全,不建议使用

  3. isShutdown

    判断是否执行了shutdown方法,此时可能线程池还在执行剩余任务。

  4. isTerminated

    是否完全终止了,即执行了shoutdown+剩余任务被执行完 或者 执行了shutdownNow

  5. awaitTermination

    awaitTermination(10) - 等待10秒,10秒内任务都执行完毕 返回 true,10秒内并未执行完毕 返回 false

线程池监控

ThreadPoolExecutor提供的线程池监控相关方法

方法 用途
getPoolSize() 获取当前线程池大小
getQueue() 返回工做队列实例,经过该实例可获取工做队列的当前大小
getLargestPoolSize() 获取工做者线程数曾经达到的最大数,该数值有助于确认线程池的最大大小设置是否合理
getActiveCount() 获取线程池中当前正在执行任务的工做者线程数(近似值)
getTaskCount() 获取线程池到目前为止所接收到的任务数(近似值)
getCompletedTaskCount() 获取线程池到目前为止已经处理完毕的任务数(近似值)