Java并发---①线程管理
一.线程管理
1.1 线程的创建方式
- 继承Thread类 (Thread类实现了Runnable接口)
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("使用继承的方式实现一个线程");
}
}
- 实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("使用实现Runnable接口的方式来实现一个线程");
}
}
1.2 线程的运行
- 如果使用的是继承Thread类的方式,直接调用start()方法即可运行一个线程
- 如果是使用实现Runnable接口的方式,那么需要创建一个Thread,将自身作为参数传入,然后调用创建的Thread对象的start()方法才可以运行。
public class Main {
public static void main(String[] args){
//第一种方式
MyThread myThread = new MyThread();
myThread.start();
//第二种方式
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
调用线程的start方法后,将会创建一个新的线程,并执行线程中的run方法
如果直接调用该对象的run方法,那么不会创建一个新的线程,而是会被主线程当成一个普通方法来执行。
1.3 线程信息的获取和设置
Thread类有一些保存信息的属性,这些属性可以用来标识线程,显示线程的状态或者控制线程的优先级。
- ID: 保存了线程的唯一标识符。
- Name:保存了线程的名字。
- Priority:保存了线程对象的优先级。线程的优先级从1到10,其中1是最低优先级,10是最高优先级。
- Status:保存了线程的状态。在Java中,线程的状态有6种:new,runnable,blocked,waiting,time waiting 或者 terminated。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("***************************");
System.out.println("id = " + Thread.currentThread().getId());
System.out.println("name = " + Thread.currentThread().getName());
System.out.println("priority = " + Thread.currentThread().getPriority());
System.out.println("state = " + Thread.currentThread().getState());
System.out.println("***************************");
}
}
public class Main {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
//设置线程名
thread.setName("线程名字");
//设置线程优先级
thread.setPriority(10);
thread.start();
}
}
输出信息如下:
***************************
id = 14
name = 线程名字
priority = 10
state = RUNNABLE
***************************
1.4 线程的中断
如果一个Java程序存在不止一个线程在执行,那么必须要等到所有的线程都执行结束后, 这个Java程序才能运行结束。更准确的说,要等到所有的非守护线程运行结束后(守护线程的概念后面会介绍),或者其中某一个线程执行了System.exit()方法时,这个Java程序才运行结束。
如果一个线程执行时间过于久,那么我们有没有什么办法让它停止呢?当然是存在的,Java为我们提供了中断机制。这个机制要求线程检测它是否被中断了,然后再决定是否响应这个中断请求。线程允许忽略中断请求并且继续执行。
public class MyThread extends Thread {
@Override
public void run(){
int result = 0;
while (true){
System.out.println("result = " + result ++);
if (this.isInterrupted()){
System.out.println("线程已经被中断");
return;
}
}
}
}
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
//启动线程
myThread.start();
try {
//让主线程休息一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
myThread.interrupt();
}
}
通过上面这个例子我们可以发行,当我们调用这个线程的interrupt方法时,线程并不会被强制中断。这个方法只会给这个线程打上一个标记,而是否中断,则由我们在 run方法中的代码所决定。
- isInterrupted 方法:获取线程的interrupted属性的值 (推荐使用此方法)
- interrupted 方法:静态方法,获取线程interrupted属性的值,并将值置为false
1.5 线程中断的控制
我们已经知道了调用线程的interrupt方法,并不能真正的让线程中断,那么我们如何才能控制线程的中断呢?
- 方法一:stop方法
stop方法能让运行中的线程强制停止下来,但是该方法已经被作废了,因为强行停止一个线程,会让很多清理工作无法进行,也会让线程获得的锁立刻释放,从而带来数据不一致等等问题。
- 方法二:interrupt + return
在外面调用线程的interrupt方法,在线程内部进行判断,当线程的interrupted属性为true时,直接return停止该线程。该方法在线程run方法的逻辑极为简单时,可以使用。但是如果线程实现了复杂的算法并分布在几个方法中,或者线程中有递归调用的方法,我们就得提供一个更好的机制来控制线程的中断。
- 方法三:interrupt + InterruptedException
下面这个示例是实现了在一个目录下,寻找某个文件的路径,里面涉及到递归调用。但是不管方法递归到了那一层,只要线程被中断了,就会抛出InterruptedException异常,并被run方法捕获到,从而进行其他的工作。
public class FileSearch implements Runnable {
private String initPath;
private String fileName;
public FileSearch(String initPath, String fileName) {
this.initPath = initPath;
this.fileName = fileName;
}
@Override
public void run() {
File file = new File(initPath);
if (file.isDirectory()){
try {
directoryProcess(file);
}catch (InterruptedException e){
System.out.printf("%s: The search has been interrupted"
, Thread.currentThread().getName());
}
}
}
private void directoryProcess(File file) throws InterruptedException {
File[] listFiles = file.listFiles();
if (listFiles != null){
for(File sonFile : listFiles){
if (sonFile.isDirectory()){
directoryProcess(sonFile);
}else {
fileProcess(sonFile);
}
}
}
if (Thread.interrupted()){
throw new InterruptedException();
}
}
private void fileProcess(File file) throws InterruptedException {
if (file.getName().equals(fileName)){
System.out.printf("%s : %s\n", Thread.currentThread().getName(), file.getAbsolutePath());
}
if (Thread.interrupted()){
throw new InterruptedException();
}
}
public class Main {
public static void main(String[] args){
FileSearch fileSearch = new FileSearch("C://", "test.txt");
Thread thread = new Thread(fileSearch);
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
thread.interrupt();
}
}
1.6 线程的休眠与恢复
有些时候,我们需要在某一个预期时间让线程进行休眠,以释放资源给其他线程使用。例如,程序的一个线程每隔一分钟就会检查一下传感器的状态,其余时间不做任何操作。在这段空间时间,我们希望线程可以不占计算机的任何资源,当它继续执行的直接到来时,JVM会选中它让它继续执行。我们可以通过sleep()方法来实现这个需求。
- sleep方法接受整型数值作为参数,以表明线程挂起执行的毫秒数。
- 通过TimeUnit枚举元素也存在一个sleep方法,这个方法也是使用Thread类的sleep方法,但是它接受的单位由选择的枚举类型而定。
public class Main {
public static void main(String[] args){
System.out.println("before sleep : " + System.currentTimeMillis());
try {
//休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep : " + System.currentTimeMillis());
System.out.println("before sleep : " + System.currentTimeMillis());
try {
//休眠一分钟
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep : " + System.currentTimeMillis());
}
}
大家可能发现了,每次执行sleep方法,都要显示的捕获一个InterruptedException异常,这是为什么呢?与下面两种场景有关
- 处于休眠状态的线程,如果调用了interrupt方法,该线程会立马从休眠状态唤醒,并抛出InterruptException异常,并且将线程的interrupted属性置为false
- 被标记了中断的线程,如果调用了sleep方法,同样也会抛出InterruptException异常,并且将线程的interrupted属性置为false
public class MyThread1 extends Thread{
@Override
public void run(){
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted : " + this.isInterrupted());
System.out.println(Thread.currentThread().getName() + " 处于休眠中的线程被调用了 interrupt方法");
System.out.println("******************");
return;
}
}
}
public class MyThread2 extends Thread {
@Override
public void run(){
while (true){
try {
if (this.isInterrupted()){
System.out.println(Thread.currentThread().getName() + "线程已经被中断");
Thread.sleep(1000);
}
}catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted : " + this.isInterrupted());
System.out.println(Thread.currentThread().getName() + " 中断的线程调用了 sleep方法");
return;
}
}
}
}
public class Main {
public static void main(String[] args){
MyThread1 myThread1 = new MyThread1();
myThread1.start();
myThread1.interrupt();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
MyThread2 myThread2 = new MyThread2();
myThread2.start();
myThread2.interrupt();
}
}
/*
结果如下:
Thread-0 interrupted : false
Thread-0 处于休眠中的线程被调用了 interrupt方法
******************
Thread-1线程已经被中断
Thread-1 interrupted : false
Thread-1 中断的线程调用了 sleep方法
*/
此外,还有另外一个方法也可以使当前线程释放暂用的CPU等资源,就是yield()方法,它将通知JVM这个线程对象可以释放CPU了。但是JVM并不保证遵循这个要求。通常来说,yield()方法只做调试使用。
1.7 等待线程的终止
在一些情况下,我们必须等待某个线程的执行结果,才能进行接下来的操作。例如,我们的程序在执行其他的任务时,必须先初始化一些必要的资源。可以使用线程来完成这些资源的加载,等待线程终止,再执行程序的其他任务。
为了达到这个目的, 我们可以使用Thread类的join()方法。当一个线程对象的join()方法被调用时,调用这个方法的线程将被挂起,直到这个线程对象完成它的任务。
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("myThread 线程开始执行: " + System.currentTimeMillis() );
try {
//休眠一分钟
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("myThread 线程执行结束: " + System.currentTimeMillis() );
}
}
public class Main {
public static void main(String[] args){
System.out.println("main 线程开始执行: " + System.currentTimeMillis() );
MyThread myThread = new MyThread();
myThread.start();
try {
//执行这段代码的线程,会等待 myThread线程的中止
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main 线程执行结束: " + System.currentTimeMillis() );
}
}
/*
执行结果:
main 线程开始执行: 1593227052702
myThread 线程开始执行: 1593227052703
myThread 线程执行结束: 1593227112704
main 线程执行结束: 1593227112704
*/
join方法的使用方式有两种
- 不带参数:join()
- 带参数:join(long milliseconds) 以及 join(long milliseconds, long nanos)
假设thread1中调用了thread2的join方法,如果是第一种方式调用的话,那么thread1将会被挂起,直到thread2执行完后才会继续运行。
如果是第二种方式调用的话,那么thread1继续运行的条件变成了两个:
①thread2执行完
②等待时间超过了指定的时间
1.8 守护线程的创建和运行
Java中存在一种特殊的线程叫做守护(Daemon)线程。这种线程的优先级很低,通常来说,当同一个程序中没有其他线程运行的时候,守护线程才会运行。
因为这种特性,守护线程通常被用来做为同一程序中的普通线程(也称为用户线程)的服务提供者。它们通常是无限循环的,以等待服务请求或者执行线程的任务,它们不能做重要的工作,因为我们不可能知道守护线程什么时候能获得CPU时钟。守护线程的主要作用就是为用户线程提供便利。
当同一个程序中已经没有用户线程在运行了,守护线程会随着JVM一起结束工作。一个典型的守护线程就是Java的垃圾回收器(Garbage Collector)。
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("MyThread线程已经启动" + System.currentTimeMillis());
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyThread线程已经结束" + System.currentTimeMillis());
}
}
public class MyDaemon extends Thread {
public MyDaemon(){
this.setDaemon(true);
}
@Override
public void run(){
System.out.println("守护线程已经启动" + System.currentTimeMillis());
while (true){
//do noting
}
}
}
public class Main {
public static void main(String[] args){
System.out.println("main线程已经启动" + System.currentTimeMillis());
MyThread myThread = new MyThread();
myThread.start();
MyDaemon myDaemon = new MyDaemon();
myDaemon.start();
System.out.println("main线程已经结束" + System.currentTimeMillis());
}
}
/*
运行结果:
main线程已经启动1593245216446
MyThread线程已经启动1593245216447
main线程已经结束1593245216447
守护线程已经启动1593245216447
MyThread线程已经结束1593245276448
*/
通过上面的例子我们可以发现,当main线程和myThread线程都结束后,守护线程也会随之结束。
此外,setDaemon()方法只能在start()方法被调用之前设置,一旦线程已经开始运行了,那么将不能再修改它的守护状态。如果在启动后,再修改其守护状态,会抛出IllegalThreadStateException异常。
我们可以通过isDaemon()方法来判断一个线程是不是守护线程。
1.9 线程中不可控制异常的处理
在Java中存在两种异常。
- 非运行时异常(Checked Exception):这种异常必须在方法声明的throws中抛出,并且在调用该方法时继续往上抛出或者使用try...catch块捕获。例如,IOExcption 和 ClassNotFoundException。
- 运行时异常(Unchecked Exception):这种异常不需要在方法声明中指定,也不需要在调用该方法时捕获。例如:NumberFormatException。
因为run()方法不支持throws语句,也就是说run()方法不能将异常往上传递,那么在run()方法中调用非运行时异常时,我们必须捕获并进行处理。那么当run()方法中抛出运行时异常时,jvm默认的行为是会在控制台输出堆栈信息,并退出程序。
但是这样也会带来问题,如果发生了运行时异常,线程就直接退出的话,那么一些清理工作无法进行,有没有什么方法能够让我们处理运行时抛出的异常呢?Java提供了这样的机制。
public class MyThread extends Thread {
@Override
public void run(){
System.out.println("MyThread 线程开始执行, " + System.currentTimeMillis());
//此处会抛出运行时异常,NumberFormatException
Integer num = Integer.valueOf("ttt");
System.out.println("MyThread 线程执行结束, " + System.currentTimeMillis());
}
}
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到了一个运行时异常");
System.out.println("线程id : " + t.getId());
System.out.println("异常错误信息如下:" + e.getMessage());
System.out.println("堆栈信息如下: ");
e.printStackTrace(System.out);
System.out.println("线程的状态为:" + t.getState());
}
}
public class Main {
public static void main(String[] args){
ExceptionHandler exceptionHandler = new ExceptionHandler();
MyThread myThread = new MyThread();
//为该线程设置一个运行时异常处理器
myThread.setUncaughtExceptionHandler(exceptionHandler);
myThread.start();
}
}
/*
运行结果如下:
MyThread 线程开始执行, 1593247344833
捕获到了一个运行时异常
线程id : 14
异常错误信息如下:For input string: "ttt"
堆栈信息如下:
java.lang.NumberFormatException: For input string: "ttt"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.valueOf(Integer.java:766)
at ex.MyThread.run(MyThread.java:7)
线程的状态为:RUNNABLE
*/
当一个线程抛出了运行时异常,jvm会去检测这个线程是否预设了运行时异常处理器,如果存在,那么jvm会调用这个处理器,并将线程对象和异常传入作为参数。
Thread类还有另一个方法可以处理运行时异常,就是静态方法setDefaultUncaughtExceptionHandler(),该方法将为所有的线程设置一个默认的运行时异常处理器。
public class Main {
public static void main(String[] args){
ExceptionHandler exceptionHandler = new ExceptionHandler();
//为所有线程设置一个默认的运行时异常处理器
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
MyThread myThread = new MyThread();
// myThread.setUncaughtExceptionHandler(exceptionHandler);
myThread.start();
}
}
/*
该方法运行结果与上面相同
*/
jvm调用运行时异常处理器的顺序:
- 首先查找该线程对象自己的运行时异常处理器
- 如果不存在,查找线程对象所在的线程组(ThreadGroup)的运行时异常处理器
- 如果还不存在,则使用默认的运行时异常处理器
1.10 线程局部变量的使用
共享数据是并发编程中最核心的问题之一。如果多个线程共享了同一个成员变量,那么你在一个线程中改变了这个值,所有的线程都会被这个改动影响。(同样,这些改动还将引来线程安全问题,下一篇再描述这个问题)
public class MyRunnable implements Runnable {
private Date startTime;
@Override
public void run() {
startTime = new Date();
System.out.println(Thread.currentThread().getId() + " 线程开始,线程的startTime的值为:" + startTime);
//让线程随机休眠0-100秒
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + " 线程结束,线程的startTime的值为:" + startTime);
}
}
public class Main {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 3; i++){
Thread thread = new Thread(myRunnable);
thread.start();
//启动一个线程,然后main休眠两秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
运行结果:
14 线程开始,线程的startTime的值为:Sat Jun 27 17:47:40 CST 2020
15 线程开始,线程的startTime的值为:Sat Jun 27 17:47:42 CST 2020
16 线程开始,线程的startTime的值为:Sat Jun 27 17:47:44 CST 2020
15 线程结束,线程的startTime的值为:Sat Jun 27 17:47:44 CST 2020
16 线程结束,线程的startTime的值为:Sat Jun 27 17:47:44 CST 2020
14 线程结束,线程的startTime的值为:Sat Jun 27 17:47:44 CST 2020
*/
通过上面这个例子我们可以发现,三个线程最后的startTime都变成了同一个值。这说明了它们共享的是同一个变量。
那么有没有什么方法,能够让每个线程不受其他线程的影响,也就是拥有自己的线程局部变量呢?Java提供的线程局部变量机制就解决了这个问题。
public class MyRunnable implements Runnable {
//定义线程局部变量,并重写初始化方法
private ThreadLocal<Date> startTime = new ThreadLocal<Date>(){
@Override
protected Date initialValue(){
return new Date();
}
};
@Override
public void run() {
System.out.println(Thread.currentThread().getId()
+ " 线程开始,线程的startTime的值为:" + startTime.get());
//让线程随机休眠0-100秒
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId()
+ " 线程结束,线程的startTime的值为:" + startTime.get());
}
}
public class Main {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 3; i++){
Thread thread = new Thread(myRunnable);
thread.start();
//启动一个线程,然后main休眠两秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
运行结果如下:
14 线程开始,线程的startTime的值为:Sat Jun 27 18:21:13 CST 2020
15 线程开始,线程的startTime的值为:Sat Jun 27 18:21:15 CST 2020
16 线程开始,线程的startTime的值为:Sat Jun 27 18:21:17 CST 2020
16 线程结束,线程的startTime的值为:Sat Jun 27 18:21:17 CST 2020
14 线程结束,线程的startTime的值为:Sat Jun 27 18:21:13 CST 2020
15 线程结束,线程的startTime的值为:Sat Jun 27 18:21:15 CST 2020
*/
通过上面的示例,我们可以发现,每个线程的startTime值都不一样。
线程局部变量的工作原理:
- 线程局部变量为每个线程存储了各自的属性值,并提供给每个线程使用
- 通过get()方法读取这个值,如果线程局部变量没有存储该线程的值,那么会调用initialValue()方法进行初始化
- 通过set()方法可以设置这个值
- 通过remove()方法可以删除已经存储的值
public class MyRunnable implements Runnable {
private ThreadLocal<Date> startTime = new ThreadLocal<Date>(){
@Override
protected Date initialValue(){
return new Date();
}
};
@Override
public void run() {
//由于线程局部变量存储的值为空,此处调用initialValue()
System.out.println("第一次获取线程局部变量" + startTime.get());
//休眠一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用set()方法,设置一个新的值
Date date = new Date();
startTime.set(date);
System.out.println("set的date为 :" + date);
//本次获取的值应该和set一样
System.out.println("第二次获取线程局部变量" + startTime.get());
//休眠一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用remove()方法,该方法会删除线程局部变量中保存的值
startTime.remove();
//本次获取和第一个次一样,由于线程局部变量存储的值为空,此处调用initialValue()
System.out.println("第三次获取线程局部变量" + startTime.get());
}
}
public class Main {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
/*
运行结果:
第一次获取线程局部变量Sat Jun 27 20:58:50 CST 2020
set的date为 :Sat Jun 27 20:58:51 CST 2020
第二次获取线程局部变量Sat Jun 27 20:58:51 CST 2020
第三次获取线程局部变量Sat Jun 27 20:58:52 CST 2020
*/
线程局部变量实际上是将共享变量拷贝了一份到线程的threadLocals变量(ThreadLocalMap类型)中,看这个类型名字也知道,类似于一个map结构,一个线程的所有的局部变量都缓存在其中。
InheritableThreadLocal类
InheritableThreadLocal类是ThreadLocal的子类,主要扩展了以下两个功能:
- 值继承:A线程创建了B线程,那么B线程就会继承A线程的InheritableThreadLocal类型的线程局部变量
- 值扩展:如果B线程实现了 childValue()方法,那么会在继承的同时,将值进行扩展
public class FatherThread extends Thread {
@Override
public void run(){
System.out.println("第一次获取父线程局部变量: " + ThreadLocalExt.str.get());
//修改父线程局部变量的值
ThreadLocalExt.str.set("test");
System.out.println("第二次获取父线程局部变量: " + ThreadLocalExt.str.get());
SonThread sonThread = new SonThread();
sonThread.start();
}
}
public class SonThread extends Thread{
@Override
public void run(){
System.out.println("子线程局部变量: " + ThreadLocalExt.str.get());
}
}
public class Main {
public static void main(String[] args){
FatherThread fatherThread = new FatherThread();
fatherThread.start();
}
}
/*
执行结果:
第一次获取父线程局部变量: 初始值
第二次获取父线程局部变量: test
子线程局部变量: test, 子线程追加的值
*/
工作原理其实与ThreadLocal类似,InheritableThreadLocal是将线程局部变量拷贝了一份到Thread对象的inheritableThreadLocals中,它与threadLocals一样,都是ThreadLocalMap类型的。在父线程中创建一个子线程时,默认的构造函数会将inheritableThreadLocals变量的值拷贝到子线程中,拷贝的方法就是childValue()方法,所以重写了childValue()就能改变子线程中的值。
1.11 线程的分组
Java还提供了一个有趣的功能,可以将线程进行分组。在同一个组内的线程,可以进行统一的访问和操作。
public class MyRunnable implements Runnable {
@Override
public void run(){
System.out.println("Thread start, name = " + Thread.currentThread().getName());
//随机休眠0-100秒
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
System.out.println("Thread end, name = " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args){
ThreadGroup threadGroup = new ThreadGroup("group");
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 5; i++){
Thread thread = new Thread(threadGroup, myRunnable);
thread.start();
//main线程休眠两秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//ThreadGroup的功能有很多,这里演示几个重要的
//①打印组内所有线程
threadGroup.list();
//②获取组内所有活跃线程的数量
int count = threadGroup.activeCount();
System.out.println("active thread : " + count);
//③将组内所有活跃线程复制到线程数组中
Thread[] threads = new Thread[count];
threadGroup.enumerate(threads);
for (Thread thread : threads){
System.out.println("name = " + thread.getName() + " , status = " + thread.getState());
}
//④中断组中所有线程,由于线程处于休眠状态,会抛出InterruptedException异常
threadGroup.interrupt();
}
}
/*
运行结果:
Thread start, name = Thread-0
Thread start, name = Thread-1
Thread start, name = Thread-2
Thread start, name = Thread-3
Thread start, name = Thread-4
java.lang.ThreadGroup[name=group,maxpri=10]
Thread[Thread-0,5,group]
Thread[Thread-1,5,group]
Thread[Thread-2,5,group]
Thread[Thread-3,5,group]
Thread[Thread-4,5,group]
active thread : 5
name = Thread-0 , status = TIMED_WAITING
name = Thread-1 , status = TIMED_WAITING
name = Thread-2 , status = TIMED_WAITING
name = Thread-3 , status = TIMED_WAITING
name = Thread-4 , status = TIMED_WAITING
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at group.MyRunnable.run(MyRunnable.java:13)
at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at group.MyRunnable.run(MyRunnable.java:13)
at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at group.MyRunnable.run(MyRunnable.java:13)
at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at group.MyRunnable.run(MyRunnable.java:13)
at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at group.MyRunnable.run(MyRunnable.java:13)
at java.lang.Thread.run(Thread.java:748)
*/
除了上面显示的这些接口外,线程组还可以设置线程的优先级,设置守护线程等等操作,线程组除了可以包含线程外,还可以包含其他的线程组,它是一个树形结构。
线程组是通过将组内所有的线程对象和线程组对象都存储起来,并且可以访问它们的信息,将统一的操作执行到所有的成员上。
1.12 线程组中不可控制异常的处理
之前我们学习了,线程中如何处理不可控制的异常(也就是运行时异常),当时也提到了线程组中也可以处理运行时异常。现在就来看看,线程组中是如何处理运行时异常的。
在覆盖线程组中的uncaughtException方法,即可在其中定义运行时异常的处理,组内成员出现了运行时异常,如果没有定义自身的异常处理器,都会进入该方法中。
public class MyRunnable implements Runnable {
@Override
public void run(){
System.out.println("Thread start, name = " + Thread.currentThread().getName());
//此处将会抛出运行时异常,NumberFormatException
Integer num = Integer.valueOf("ttt");
System.out.println("Thread end, name = " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args){
ThreadGroup threadGroup = new ThreadGroup("group"){
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到了一个运行时异常");
System.out.println("线程id : " + t.getId());
System.out.println("异常错误信息如下:" + e.getMessage());
System.out.println("堆栈信息如下: ");
e.printStackTrace(System.out);
System.out.println("线程的状态为:" + t.getState());
}
};
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(threadGroup, myRunnable);
thread.start();
}
}
/*
Thread start, name = Thread-0
捕获到了一个运行时异常
线程id : 14
异常错误信息如下:For input string: "ttt"
堆栈信息如下:
java.lang.NumberFormatException: For input string: "ttt"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.valueOf(Integer.java:766)
at group.MyRunnable.run(MyRunnable.java:12)
at java.lang.Thread.run(Thread.java:748)
线程的状态为:RUNNABLE
*/
jvm调用运行时异常处理器的顺序:
- 首先查找该线程对象自己的运行时异常处理器
- 如果不存在,查找线程对象所在的线程组(ThreadGroup)的运行时异常处理器
- 如果还不存在,则使用默认的运行时异常处理器
1.13 使用工厂类创建线程
工程模式是设计模式中最常用的模式之一。它是一个建造者模式,使用一个类为其他的一个类或者多个类创建对象。当我们为这些类创建对象时,不需要再使用new构造器,而直接使用工厂。
使用工厂模式,可以将对象的创建集中化,有以下几点好处:
- 更容易修改创建对象的方式
- 更容易为有限资源限制创建对象的数目。例如,我们可以限制一个类只能创建n个对象。
- 更容易为创建的对象生产统计数据。
Java本身已经为我们提供了线程的工厂类,那就是ThreadFactory接口,这个接口实现了线程对象工厂。Java并发API的高级工具类也使用了线程工厂创建线程。
public class MyRunnable implements Runnable{
@Override
public void run() {
//do noting
}
}
public class MyThreadFactory implements ThreadFactory {
/**
* 统计创建的线程数量
*/
private int count;
/**
* 统计线程创建的信息
*/
private List<String> stats = new ArrayList<>();
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
count++;
stats.add("Create thread: id = " + thread.getId() + " , name = " + thread.getName());
return thread;
}
public String getStatus(){
StringBuffer stringBuffer = new StringBuffer();
Iterator<String> iterator = stats.iterator();
while (iterator.hasNext()){
stringBuffer.append(iterator.next() + "\n");
}
return stringBuffer.toString();
}
}
public class Main {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
MyThreadFactory myThreadFactory = new MyThreadFactory();
for (int i = 0; i < 5; i++){
Thread thread = myThreadFactory.newThread(myRunnable);
thread.start();
}
System.out.println(myThreadFactory.getStatus());
}
}
/*
运行结果:
Create thread: id = 14 , name = Thread-0
Create thread: id = 15 , name = Thread-1
Create thread: id = 16 , name = Thread-2
Create thread: id = 17 , name = Thread-3
Create thread: id = 18 , name = Thread-4
*/
ThreadFactory接口只有一个方法,就是newThread,它以Runnable接口作为入参,返回一个创建好的线程。当实现ThreadFactory接口时,必须实现这个方法。