Thread|Thread 基础知识

线程 线程(Thread)是java程序运行的基本调度单元; 在进行JUC的源码分析之前, 想回顾一下Thread的基本属性, 及方法;
1. 线程 State:

/** * Created by xjk on 1/12/17. */ public enum State { /** 线程刚刚创建时的状态, 马上到 RUNNABLE */ NEW,/** 线程初始化OK, 开始执行任务(run) */ RUNNABLE,/** * 阻塞状态, 千万别和WAITING状态混淆 * 这种状态是线程在等待 JVM monitor lock(通俗一点 就是等待执行 synchronous 里面的代码) * 这和 LockSupport 没半毛钱关系 */ BLOCKED,/** * 线程的等待状态, 导致线程进入这种状态通常是下面三个方法 * 1. Object.wait() * 2. Thread.join() * 3. LockSupport.park() */ WAITING,/** * 这也是线程的等待状态, 和WAITING差不多, 只是这个有timeout而已, 通常由下面四种方法导致 * 1. Object.wait(long timeout) * 2. Thread.join(long timeout) * 3. LockSupport.parkNanos(long timeout) * 4. LockSupport.parkUntil(long timeout) */ TIMED_WAITING,/** * 线程执行ok */ TERMINATED }

这些状态何时有用, 我又何时见到它们呢? 对, 一般线程出问题就会想到它们了(比如 程序死锁, 直接运行 jstack 查看线程堆栈, 就能大体判断问题出在哪个线程, 哪段代码)
2. 常用方法
/** 等待 */ Object.wait(); /** 通知 */ Object.notify(); /** 悬挂 */ Thread.suspend(); /** 重用 */ Thread.resume(); /** 等待x线程执行后, 当前线程再执行 */ Thread.join(); /** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * 这段英语大体意思: 给调度器发送信息, 当前线程推出CPU调度(这个不是指当前线程不执行任务) * 调用这个方法后, 当前线程会先推出任务调度, 然后再重新抢夺CPU, 但能不能抢到就不一定了 * 通产用于, 当前线程占用较多资源, 但任务又不紧急的情况(concurrent包中的源码会提及) */ Thread.yield();

这几个是线程的基本用法, 但现在用得比较少, 为啥?
  1. jdk中有了更好用的 concurrent 包开进行多线程开发
  2. 使用这些方法有时会出现奇怪的事件(尤其和 concurrent 包中的工具类混用, 匪夷所思)
    ps: 若想用这些方法来写 Future, Promise 等工具类 可以 参考Netty 4.x系列
3. 线程中断 线程中断是java中线程协作的重要机制, 而所谓的中断其实只是给线程设置中断标示, 并且唤醒正在waiting状态的线程;
线程中断相关的主要有三个方法:
/** * 作用: 中断线程, * 若线程 sleep 或 wait 时调用此方法, * 则抛出 InterruptedException 异常, 并且会清除中断标记 * (ps 重点来了, 若通过 LockSupport阻塞线程, 则不会抛出异常, 并且不会清除线程的中断标记, 这在 concurrent 包里面充分利用了这个机制) * * 比如先通过 LockSupport.park(this) 来中断, 而后其他线程释放lock时, 唤醒这个线程, 这时再调用 Thread.interrupted() 返回中断标示(调用此方法会清除中断标示) *这时外面的函数会根据 parkAndCheckInterrupt() 函数的返回值判断线程的唤醒是被 interrupted 还是正常的唤醒(LockSupport.unpark()) 来决定后续的策略 * private final boolean parkAndCheckInterrupt() { *LockSupport.park(this); *return Thread.interrupted(); * } */ Thread.interrupt(); /** * 判断当前的线程是否中断, 返回 true/false */ Thread.isInterrupted(); /** * 判断当前的线程是否中断, 并且清除中断标示(注意这里是 interrupted, 和上面的 interrupt是不一样的) */ Thread.interrupted();

【Thread|Thread 基础知识】线程的中断是 Java 并发开发中非常重要的机制, concurrent 包中的很多工具类的方法都是通过这个机制来安全退出;
下面来段代码示例一下:
import org.apache.log4j.Logger; import java.util.concurrent.locks.LockSupport; /** * Created by xjk on 1/13/17. */ public class ThreadTest {private static final Logger logger = Logger.getLogger(ThreadTest.class); public static void main(String[] args) throws Exception{Thread t1 = new Thread(){ @Override public void run() { while(true){ if(Thread.currentThread().isInterrupted()){ logger.info("线程中断, 退出loop"); break; }try { Thread.sleep(5*1000); } catch (InterruptedException e) { e.printStackTrace(); logger.info("线程在 waiting 状态时收到中断信息"); logger.info("此时线程中断标示: " + Thread.currentThread().isInterrupted()); // 再次点用线程中断, 这时就又有中断标示 Thread.currentThread().interrupt(); }Thread.yield(); } logger.info("1. 此时线程中断标示: " + Thread.currentThread().isInterrupted()); // 再次调用程序阻塞, 看看是否有用 LockSupport.park(this); logger.info("2. 此时线程中断标示: " + Thread.currentThread().isInterrupted()); try { Thread.currentThread().sleep(5*1000); } catch (InterruptedException e) { logger.info("在线程中断时调用sleep 抛异常"); e.printStackTrace(); }logger.info("3. 此时线程中断标示: " + Thread.currentThread().interrupted()); } }; t1.start(); Thread.sleep(2 *1000); t1.interrupt(); } }

这段代码主要测试线程中断的几个函数功能, 建议自己写一下, 在运行程序时, 会发现 程序中的'LockSupport.park(this); ' 没起作用, 而且还没清除中断标记; 但此时调用 Thread.sleep(long timeout), 则会因为中断标示的存在而抛出异常(抛出异常后中断标示也被清除);
执行结果
java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:26) [2017-01-13 22:23:03,903] INFOThread-0 (ThreadTest.java:29) - 线程在 waiting 状态时收到中断信息 [2017-01-13 22:23:03,906] INFOThread-0 (ThreadTest.java:30) - 此时线程中断标示: false [2017-01-13 22:23:03,907] INFOThread-0 (ThreadTest.java:21) - 线程中断, 退出loop [2017-01-13 22:23:03,907] INFOThread-0 (ThreadTest.java:37) - 1. 此时线程中断标示: true [2017-01-13 22:23:03,908] INFOThread-0 (ThreadTest.java:41) - 2. 此时线程中断标示: true [2017-01-13 22:23:03,909] INFOThread-0 (ThreadTest.java:45) - 在线程中断时调用sleep 抛异常 [2017-01-13 22:23:03,909] INFOThread-0 (ThreadTest.java:50) - 3. 此时线程中断标示: false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:43)

4. Daemon进程 Daemon线程: 守护线程通常是在后台默默干一些苦活, 比如垃圾回收; 守护线程有个特点, 就是当那些daemon=false的线程都退出, 则daemon也退出
直接上代码:
import org.apache.log4j.Logger; /** * Created by xjk on 1/13/17. */ public class DaemonThreadTest {private static final Logger logger = Logger.getLogger(DaemonThreadTest.class); static Thread t1 = new Thread(){ @Override public void run() { while(true){ logger.info("I am alive"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }@Override protected void finalize() throws Throwable { super.finalize(); logger.info("我要退出程序了"); } }; public static void main(String[] args) throws Exception{ t1.setDaemon(true); t1.start(); Thread.sleep(3 * 1000); logger.info("main 方法执行OK"); }@Override protected void finalize() throws Throwable { super.finalize(); logger.info("DaemonThreadTest 退出"); }}

线程t1 daemon=true, 所以在主线程退出后, 自己也退出, 自己在运行时,可以将 "t1.setDaemon(true)" 注释掉再看看效果
ps:
  1. finalize在java中不一定会执行
  2. daemon 默认值是 false
console:
[2017-01-13 22:43:27,605] INFOThread-0 (DaemonThreadTest.java:16) - I am alive [2017-01-13 22:43:28,612] INFOThread-0 (DaemonThreadTest.java:16) - I am alive [2017-01-13 22:43:29,615] INFOThread-0 (DaemonThreadTest.java:16) - I am alive [2017-01-13 22:43:30,606] INFOmain (DaemonThreadTest.java:38) - main 方法执行OKProcess finished with exit code 0

总结: 线程中断机制在整个并发编程中起着非常大的作用, 尤其和 LockSupport 配合使用
参考资料:
skywang12345 线程 interrupt
zhanjindong LockSupport和Interrupt (这篇写得相当好)

    推荐阅读