头图

线程管理

eacape
English

线程组

线程组(ThreadGroup)可以用来表示一系列相似或者相关的线程集合。

一个线程组可以包含多个线程和线程组,一个线程组包含另外的线程组那么这个线程组被称为另外这些线程组的父线程组,如过一个线程没有被关联线程组,那么这个线程就属于其父线程所属的线程组,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

线程池

使用线程池的好处

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

线程池中各个参数的含义

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

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

什么是拒绝策略

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

常见的几种拒绝策略

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

常见线程池

  1. FixedThreadPool

    固定线程数的线程池,核心线程数和最大线程数相同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()获取线程池到目前为止已经处理完毕的任务数(近似值)
阅读 528

JAVA 攻城狮

176 声望
4 粉丝
0 条评论

JAVA 攻城狮

176 声望
4 粉丝
文章目录
宣传栏