Java并发编程中,其中一个难点是对线程生命周期的理解,和多种线程控制方法、线程沟通方法的灵活运用。这些方法和概念之间彼此联系紧密,共同构成了Java并发编程基石之一。
Java线程的生命周期
Java线程类定义了New、Runnable、Running Man、Blocked和Dead五种状态。
New
当初始化了一个线程对象之后,线程就进入了New的状态。此时JVM会为其分配堆内存、初始化成员变量的值,获取当前线程为父线程。
Runnable
当调用线程对象的start方法之后,就进入Runnable状态。JVM会为其创建虚拟机栈和程序计数器。此时仅表明线程可以开始运行,但何时运行取决于JVM的线程调度器。
Running
当线程获取到CPU资源的时候,就进入Running状态执行线程方法了。如果线程数大于多处理器的数目,会存在多个线程轮换,尽管多个处理器会同时并行处理几个线程。
线程调度的细节取决于底层平台,当Running的线程调用其yield方法或失去CPU资源的时候,即回到Runnable状态。
Blocked
当发生如下情况,线程会被阻塞/重新进入Runnable状态:
1. 线程调用sleep等可中断方法 ===> sleep方法经过指定时间/其它线程调用了阻塞线程的interrupt方法
2. 线程调用了一个阻塞式IO ===> 调用的阻塞式IO方法返回
3. 试图获取一个正被使用的同步锁 ===> 成功获取同步锁
4. 调用了wait/await方法 ===> 其它线程发出了notify/notifyAll/signal/signalAll
5. 调用了其它线程的join方法,进入阻塞 ===> 调用了join线程的线程执行体完成或死亡
6. 线程调用suspend方法(容易导致死锁,不建议使用) ===> 被调用了resume方法
Dead
当发生如下情况,线程结束
1. 线程执行体完成
2. 抛出未捕获的异常或错误
3. 直接调用stop方法(容易导致死锁,不建议使用)
可通过调用线程对象的isAlive方法,如果处于新建和死亡状态会返回false
线程管理
常用的线程管理包括设置后台线程、设置优先级、join、sleep、yield、以及interrupt
设置后台线程
setDaemon(true):设置为后台线程
isDaemon():用于判断指定线程是否为后台线程
设置优先级
setPriority(int priority):设置优先级
getPriority():获取优先级
1 public class ThreadPriority { 2 3 public static void main(String[] args) { 4 Thread t1 = new Thread(()-> 5 { 6 while(true) { 7 System.out.println("t11111"); 8 } 9 }, "t1"); 10 t1.setPriority(Thread.NORM_PRIORITY); 11 12 Thread t2 = new Thread(()->{ 13 while (true) { 14 System.out.println("t22222"); 15 } 16 }); 17 t2.setPriority(10); 18 19 t1.start(); 20 t2.start(); 21 } 22 23 }
join
在某个程序执行流中(如main线程),调用其它线程的join方法,调用线程(如main线程)将被阻塞,直到被join方法加入的线程执行完为止。
join线程可以理解为把一个问题分为若干小问题由不同的线程处理,在其它线程处理完毕后,再回到运行状态的概念。
1 public class ThreadJoin { 2 3 private static void shortSleep() { 4 try { 5 TimeUnit.SECONDS.sleep(1); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 } 10 11 private static Thread create(int seq) { 12 return new Thread(() -> { 13 for (int i = 0; i < 10; i++) { 14 System.out.println(Thread.currentThread().getName() + "#" + i); 15 shortSleep(); 16 } 17 }, String.valueOf(seq)); 18 } 19 20 public static void main(String[] args) throws InterruptedException { 21 22 List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList()); 23 24 threads.forEach(Thread::start); 25 //在main线程中, 依次调用thread的join方法, main会进入阻塞, 等其它线程完成了再行继续 26 for(Thread thread : threads) { 27 thread.join(); 28 } 29 30 for(int i = 0; i < 10; i++) { 31 System.out.println(Thread.currentThread().getName() + "#" + i); 32 shortSleep(); 33 } 34 35 } 36 37 }
sleep
Thread类的静态方法,用于暂停线程的执行。一般建议使用1.5后新增的TimeUnit类来更好的暂停线程
1 public class ThreadSleep { 2 3 private static void sleep(int ms) { 4 try { 5 TimeUnit.SECONDS.sleep(ms); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 } 10 11 public static void main(String[] args) { 12 13 new Thread(() -> 14 { 15 long startTime = System.currentTimeMillis(); 16 sleep(2); 17 long endTime = System.currentTimeMillis(); 18 System.out.println(String.format("Total spend %d second", (endTime - startTime))); 19 }).start(); 20 21 /* 22 * Thread sleep times depends on your system 23 */ 24 long startTime = System.currentTimeMillis(); 25 sleep(3); 26 long endTime = System.currentTimeMillis(); 27 System.out.println(String.format("Main thread total spend %d second", (endTime - startTime))); 28 29 } 30 }
interrupt
interrupt方法,主要是另外一个线程用于打断一个阻塞状态的线程,使其重新进入Runnable状态。如果一个阻塞方法(如sleep、wait)可以被interrupt至Runnable状态,这些方法又叫“可中断方法”。interrupt包括3个方法:
1. void interrupt()
2. static boolean interrupted()
3. boolean isInterrupted()
1 public class ThreadInterrupt { 2 3 public static void main(String[] args) throws InterruptedException { 4 Thread thread = new Thread() { 5 public void run() { 6 while(true) { 7 try { 8 /* 9 * sleep为可中断方法, 因此会擦除interrupt标志; 10 * 如果注释掉sleep方法, thread收到中断信号后,interrupt标志不会被移除 11 */ 12 TimeUnit.MINUTES.sleep(2); 13 } catch (InterruptedException e) { 14 System.out.printf("I am be interrupted? %s\n", isInterrupted()); 15 } 16 } 17 } 18 }; 19 thread.setDaemon(true); 20 thread.start(); 21 //确保thread线程启动完毕 22 TimeUnit.SECONDS.sleep(2); 23 System.out.printf("I am be interrupted? %s\n", thread.isInterrupted()); 24 //在main线程中, 调用线程对象thread的interrupt()方法, 中断thread线程的sleep阻塞状态. 25 thread.interrupt(); 26 //确保thread线程进入Runnable并被线程调度器调动 27 TimeUnit.SECONDS.sleep(2); 28 System.out.printf("I am be interrupted? %s\n", thread.isInterrupted()); 29 } 30 }
事实上,在Thread中维护了一个interrupt的flag,它对线程对象的影响在于:
1. 如果一个线程在运行过程中,另外一个线程调用interrupt方法,相当于似得interrupt的flag为true(上面的代码中,注释掉thread线程的sleep代码,可以看到28结果为true)
2. 如果一个线程因为可中断方法而导致的阻塞,另一个线程调用interrupt方法,阻塞线程捕获中断信号,会把interrupt的flag改为false,实现重置。
static boolean interrupted()方法除了判断线程是否中断外,会直接把interrupt的flag变为true。
1 public class ThreadInterrupted { 2 3 public static void main(String[] args) throws InterruptedException { 4 5 Thread thread = new Thread(() -> { 6 while (true) { 7 if(Thread.interrupted()) { 8 System.out.println("Yes my interrupt flag has been removed!"); 9 }else { 10 System.out.println("I am running"); 11 } 12 } 13 }); 14 15 thread.setDaemon(true); 16 thread.start(); 17 18 //Main sleep to make sure thread start 19 TimeUnit.MILLISECONDS.sleep(2); 20 thread.interrupt(); 21 //以下显示false 22 System.out.println("Main thread is interrupted? " + Thread.interrupted()); 23 24 Thread.currentThread().interrupt(); 25 //以下显示true 26 System.out.println("Main thread is interrupted? " + Thread.currentThread().isInterrupted()); 27 28 try { 29 TimeUnit.MINUTES.sleep(2); 30 } catch (Exception e) { 31 System.out.println("I am be interrupted!!!!!!"); 32 } 33 } 34 }
yield
与sleep方法不同,yield方法只是让当前线程暂停一下,以便线程调度器操作线程调度。该方法不会让线程进入阻塞
1 public class ThreadYield { 2 3 private static Thread create(int index) { 4 try { 5 TimeUnit.SECONDS.sleep(1); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 return new Thread(()-> 10 { 11 if (index == 0) { 12 Thread.yield(); 13 } 14 System.out.println(index); 15 }); 16 } 17 18 public static void main(String[] args) { 19 IntStream.range(0, 2).mapToObj(ThreadYield::create).forEach(Thread::start); 20 } 21 }
线程关闭
线程关闭不建议使用stop方法,一般来说,线程关闭分为正常和异常两种情况。本文不讨论线程执行体正常完成退出以及异常抛出这两种情况,就业务逻辑中介绍两种主动关闭线程的方法。
1. 通过捕获interrupt中断信号来关闭线程
1 public class InterruptThreadExit { 2 3 public static void main(String[] args) throws InterruptedException { 4 5 Thread t = new Thread() { 6 @Override 7 public void run() { 8 System.out.println("I will start to work"); 9 //此处通过IsInterrupt或interrupted方法来判断中断状态, 以便控制while循环实现控制线程关闭的目的 10 while(!interrupted()) { 11 //System.out.println(isInterrupted()); 12 } 13 System.out.println(isInterrupted()); 14 System.out.println("I will exit"); 15 } 16 }; 17 t.start(); 18 TimeUnit.SECONDS.sleep(3); 19 System.out.println("ready to send interrupt signal"); 20 t.interrupt(); 21 } 22 }
2. 通过volatile设置关闭开关
更一般地,可以使用volatile的可见性特点,设置线程控制开关,来控制线程的关闭
1 /* 2 * 通过volatile修饰符, 设置开关来控制线程关闭 3 */ 4 public class FlagThreadExit { 5 6 static class MyTask extends Thread{ 7 //使用volatile修饰一个boolean开关变量 8 private volatile boolean closed = false; 9 public void run() { 10 System.out.println("I will start to work"); 11 //通过开变量和中断标识, 以便控制while循环实现控制线程关闭的目的 12 while(!closed && !interrupted()) { 13 //working 14 } 15 System.out.println("I will exit"); 16 } 17 //定义用于关闭的close方法, 设置开关变量和调用中断方法 18 public void close() { 19 closed = true; 20 interrupt(); 21 } 22 } 23 24 public static void main(String[] args) throws InterruptedException { 25 MyTask t = new MyTask(); 26 t.start(); 27 TimeUnit.SECONDS.sleep(2); 28 System.out.println("use close method"); 29 t.close(); 30 } 31 }