Java 多线程(七)——线程组与线程池

1 线程组

1.1 概述

  • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理。对线程组的控管理,即同时控制线程组里面的这一批线程。
  • 用户创建的所有线程都属于指定线程组,如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即main线程组)。默认情况下,子线程和父线程处于同一个线程组。
  • 只有在创建线程时才能指定其所在的线程组,线程运行中途不能改变它所属的线程组,也就是说线程一旦指定所在的线程组,就直到该线程结束。
  • 线程组与线程之间结构类似于树形的结构:
    这里写图片描述

1.2 API介绍

1.2.1 Thread类

  • Thread(ThreadGroup group,Runnable target):group属于的线程组,target为新线程
  • Thread(ThreadGroup group,Runnable target,String name):group属于的线程组,target为新线程,name:线程名
  • Thread(ThreadGroup group,String name):新线程名为name,属于group线程组

1.2.2 ThreadGroup类

(1)构造方法

  • ThreadGroup(String name):以指定线程组名字来创建新线程组
  • ThreadGroup(ThreadGroup parent,String name):以指定的名字、指定的父线程组来创建一个新线程组。

(2)常用操作方法

  • int activeCount():获取线程组中活动线程的数量
  • interrupt():中断线程组中所有线程
  • isDaemon():是否为后台线程组
  • setDaemon(boolean daemon):设置为后台线程组
  • setMaxPriority(int pri):设置线程组的最高优先级

1.3 简单示例

package com.jtzen9;

public class Main {
    public static void main(String[] args) {

        // 获取主线程所在的线程组,这是所有线程默认的线程组
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        System.out.println("主线程组的名字:" + mainGroup.getName());
        System.out.println("主线程组是否是后台线程组:" + mainGroup.isDaemon());

        new MyThread("主线程组的线程").start();

        ThreadGroup tg = new ThreadGroup("新线程组");
        tg.setDaemon(true);
        System.out.println("tg线程组是否是后台线程组:" + tg.isDaemon());

        MyThread tt = new MyThread(tg , "tg组的线程甲");
        tt.start();

        new MyThread(tg , "tg组的线程乙").start();
    }
}

class MyThread extends Thread {
    // 提供指定线程名的构造器
    public MyThread(String name) {
        super(name);
    }
    // 提供指定线程名、线程组的构造器
    public MyThread(ThreadGroup group , String name) {
        super(group, name);
    }
    public void run() {
        for (int i = 0; i < 20 ; i++ ) {
            System.out.println(getName() + " 线程的i变量" + i);
        }
    }
}

输出结果:

主线程组的名字:main
主线程组是否是后台线程组:false
tg线程组是否是后台线程组:true
主线程组的线程 线程的i变量0
主线程组的线程 线程的i变量1
主线程组的线程 线程的i变量2
主线程组的线程 线程的i变量3
主线程组的线程 线程的i变量4
tg组的线程甲 线程的i变量0
tg组的线程甲 线程的i变量1
tg组的线程甲 线程的i变量2
tg组的线程甲 线程的i变量3
tg组的线程甲 线程的i变量4
tg组的线程乙 线程的i变量0
tg组的线程乙 线程的i变量1
tg组的线程乙 线程的i变量2
tg组的线程乙 线程的i变量3
tg组的线程乙 线程的i变量4

1.4 未处理的异常

  ThreadGroup内定义了一个方法:void uncaughtException(Thread t,Throwable e),该方法可以处理该线程组内的任意线程所抛出的未处理异常。
  ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。当一个线程抛出未处理异常时,JVM会首先查找该异常对应的异常处理器处理该异常;否则,JVM将会调用该线程所属的线程组对象的uncaughtException()方法来处理该异常。
例子:

package com.jtzen9;

public class Main {
    public static void main(String[] args) {
        // 设置主线程的异常处理器
        Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());
        int a = 5 / 0;
        System.out.println("程序正常结束!");
    }
}
// 定义自己的异常处理器
class MyExHandler implements Thread.UncaughtExceptionHandler {
    // 实现uncaughtException方法,该方法将处理线程的未处理异常
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println(t + " 线程出现了异常:" + e);
    }
}

输出:

Thread[main,5,main] 线程出现了异常:java.lang.ArithmeticException: / by zero

2 线程池

  与数据库连接池类似,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法,当执行完毕后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

  • Executors工厂类:该工厂类包含多个静态工厂方法来创建线程池。
    ① newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。
    ② newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池
    ③ newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。
    ④ newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争。
  • ExecutorService类:代表尽执行线程的线程池,即只要线程池中有空闲线程,就立即执行线程任务
  • ScheduledExecutorService类:代表可在指定延迟后或周期性地执行线程任务的线程池。

使用线程池来执行线程任务的步骤如下:

  1. 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
  2. 创建Runnable实现类或Callable实现类的实例,作为线程执行任务
  3. 调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例
  4. 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池

例子:

package com.jtzen9;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws Exception{
        // 创建一个具有固定线程数的线程池
        ExecutorService pool = Executors.newFixedThreadPool(6);
        // 使用Lambda表达式创建Runnable对象
        Runnable target = () ->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "的i值为:" + i);
            }
        };
        //  向线程池中提交两个线程
        pool.submit(target);
        pool.submit(target);
        // 关闭线程池
        pool.shutdown();
    }
}

输出:

pool-1-thread-1的i值为:0
pool-1-thread-2的i值为:0
pool-1-thread-1的i值为:1
pool-1-thread-2的i值为:1
pool-1-thread-1的i值为:2
pool-1-thread-2的i值为:2
pool-1-thread-1的i值为:3
pool-1-thread-2的i值为:3
pool-1-thread-1的i值为:4
pool-1-thread-2的i值为:4
pool-1-thread-1的i值为:5
pool-1-thread-2的i值为:5
pool-1-thread-1的i值为:6
pool-1-thread-1的i值为:7
pool-1-thread-1的i值为:8
pool-1-thread-2的i值为:6
pool-1-thread-1的i值为:9
pool-1-thread-2的i值为:7
pool-1-thread-2的i值为:8
pool-1-thread-2的i值为:9

3 线程组和线程池的区别

  • 线程组是为了方便线程的管理
  • 线程池是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。

4 ForkJoinPool类

  为了充分利用多CPU、多核CPU的优势,可以考虑把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可
  ForkJoinPool类支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。
  ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。创建了ForkJoinPool类实例,就可以调用ForkJoinPool的submit(ForkJoinTask task)invoke(ForkJoinTask task)方法来执行指定任务。其中ForkJoinTask代表一个可以并行、合并的任务。
  ForkJoinTask是一个抽象类,有两个抽象子类:RecursiveAction(代表没有返回值的任务)和RecursiveTask(代表有返回值的任务)。

4.1 有返回值的任务

package com.jtzen9;

import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

public class Main {
    public static void main(String[] args) throws Exception{
        int[] arr = new int[100];
        Random rand = new Random();
        int total = 0;
        // 初始化100个数字元素
        for (int i = 0 , len = arr.length; i < len ; i++ )
        {
            int tmp = rand.nextInt(20);
            // 对数组元素赋值,并将数组元素的值添加到sum总和中。
            total += (arr[i] = tmp);
        }
        System.out.println(total);
        // 创建一个通用池
        ForkJoinPool pool = ForkJoinPool.commonPool();
        // 提交可分解的CalTask任务
        Future<Integer> future = pool.submit(new CalTask(arr , 0 , arr.length));
        System.out.println(future.get());
        // 关闭线程池
        pool.shutdown();
    }
}
// 继承RecursiveTask来实现"可分解"的任务
class CalTask extends RecursiveTask<Integer> {
    // 每个“小任务”只最多只累加20个数
    private static final int THRESHOLD = 20;
    private int arr[];
    private int start;
    private int end;
    // 累加从start到end的数组元素
    public CalTask(int[] arr , int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }
    @Override
    protected Integer compute() {
        int sum = 0;
        // 当end与start之间的差小于THRESHOLD时,开始进行实际累加
        if(end - start < THRESHOLD) {
            for (int i = start ; i < end ; i++ ) {
                sum += arr[i];
            }
            return sum;
        }
        else {
            // 如果当end与start之间的差大于THRESHOLD时,即要累加的数超过20个时
            // 将大任务分解成两个小任务。
            int middle = (start + end) / 2;
            CalTask left = new CalTask(arr , start, middle);
            CalTask right = new CalTask(arr , middle, end);
            // 并行执行两个“小任务”
            left.fork();
            right.fork();
            // 把两个“小任务”累加的结果合并起来
            return left.join() + right.join();    // ①
        }
    }
}
942
942

4.2 没有返回值的任务

package com.jtzen9;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws Exception{
        ForkJoinPool pool = new ForkJoinPool();
        // 提交可分解的PrintTask任务
        pool.submit(new PrintTask(0 , 300));
        pool.awaitTermination(2, TimeUnit.SECONDS);
        // 关闭线程池
        pool.shutdown();
    }
}
// 继承RecursiveAction来实现"可分解"的任务
class PrintTask extends RecursiveAction {
    // 每个“小任务”只最多只打印50个数
    private static final int THRESHOLD = 50;
    private int start;
    private int end;
    // 打印从start到end的任务
    public PrintTask(int start, int end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected void compute() {
        // 当end与start之间的差小于THRESHOLD时,开始打印
        if(end - start < THRESHOLD) {
            for (int i = start ; i < end ; i++ ) {
                System.out.println(Thread.currentThread().getName()
                        + "的i值:" + i);
            }
        }
        else {
            // 如果当end与start之间的差大于THRESHOLD时,即要打印的数超过50个
            // 将大任务分解成两个小任务。
            int middle = (start + end) / 2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            // 并行执行两个“小任务”
            left.fork();
            right.fork();
        }
    }
}

部分输出:

ForkJoinPool-1-worker-1的i值:262
ForkJoinPool-1-worker-1的i值:263
ForkJoinPool-1-worker-1的i值:264
ForkJoinPool-1-worker-1的i值:265
ForkJoinPool-1-worker-1的i值:266
ForkJoinPool-1-worker-2的i值:112
ForkJoinPool-1-worker-2的i值:113
ForkJoinPool-1-worker-2的i值:114
ForkJoinPool-1-worker-3的i值:37
ForkJoinPool-1-worker-1的i值:267
ForkJoinPool-1-worker-3的i值:42
ForkJoinPool-1-worker-2的i值:120
ForkJoinPool-1-worker-3的i值:43
ForkJoinPool-1-worker-0的i值:75
ForkJoinPool-1-worker-2的i值:121
ForkJoinPool-1-worker-0的i值:76
ForkJoinPool-1-worker-1的i值:268
ForkJoinPool-1-worker-3的i值:44
ForkJoinPool-1-worker-1的i值:269
ForkJoinPool-1-worker-0的i值:77
ForkJoinPool-1-worker-2的i值:122
ForkJoinPool-1-worker-0的i值:78
ForkJoinPool-1-worker-0的i值:79
ForkJoinPool-1-worker-0的i值:80
ForkJoinPool-1-worker-0的i值:81
ForkJoinPool-1-worker-0的i值:82
ForkJoinPool-1-worker-0的i值:83
ForkJoinPool-1-worker-0的i值:84
ForkJoinPool-1-worker-0的i值:85
ForkJoinPool-1-worker-0的i值:86
ForkJoinPool-1-worker-0的i值:87
ForkJoinPool-1-worker-0的i值:88
ForkJoinPool-1-worker-1的i值:270
ForkJoinPool-1-worker-3的i值:45
ForkJoinPool-1-worker-1的i值:271
ForkJoinPool-1-worker-0的i值:89
ForkJoinPool-1-worker-2的i值:123
ForkJoinPool-1-worker-0的i值:90
ForkJoinPool-1-worker-1的i值:272
ForkJoinPool-1-worker-3的i值:46
ForkJoinPool-1-worker-1的i值:273
ForkJoinPool-1-worker-0的i值:91
ForkJoinPool-1-worker-2的i值:124
ForkJoinPool-1-worker-0的i值:92


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值