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方法经过指定时间
2. 线程调用了一个阻塞式IO ===> 调用的阻塞式IO方法返回
3. 试图获取一个正被使用的同步锁 ===> 成功获取同步锁
4. 等待notify ===> 其它线程发出了notify
5. 线程调用suspend方法(容易导致死锁,不建议使用) ===> 被调用了resume方法
Dead
当发生如下情况,线程结束
1. 线程执行体完成
2. 抛出未捕获的异常或错误
3. 直接调用stop方法(容易导致死锁,不建议使用)
可通过调用线程对象的isAlive方法,如果处于新建和死亡状态会返回false
线程管理
常用的线程管理包括设置后台线程、设置优先级、join、sleep和yield
设置后台线程
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
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线程调用join方法, 会进入阻塞, 等其它线程完成了再行继续 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 }
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 }
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 }