线程
线程(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();
这几个是线程的基本用法, 但现在用得比较少, 为啥?
- jdk中有了更好用的 concurrent 包开进行多线程开发
- 使用这些方法有时会出现奇怪的事件(尤其和 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();
线程的中断是 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] INFO Thread-0 (ThreadTest.java:29) - 线程在 waiting 状态时收到中断信息
[2017-01-13 22:23:03,906] INFO Thread-0 (ThreadTest.java:30) - 此时线程中断标示: false
[2017-01-13 22:23:03,907] INFO Thread-0 (ThreadTest.java:21) - 线程中断, 退出loop
[2017-01-13 22:23:03,907] INFO Thread-0 (ThreadTest.java:37) - 1. 此时线程中断标示: true
[2017-01-13 22:23:03,908] INFO Thread-0 (ThreadTest.java:41) - 2. 此时线程中断标示: true
[2017-01-13 22:23:03,909] INFO Thread-0 (ThreadTest.java:45) - 在线程中断时调用sleep 抛异常
[2017-01-13 22:23:03,909] INFO Thread-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:
- finalize在java中不一定会执行
- daemon 默认值是 false
console:
[2017-01-13 22:43:27,605] INFO Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:28,612] INFO Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:29,615] INFO Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:30,606] INFO main (DaemonThreadTest.java:38) - main 方法执行OK
Process finished with exit code 0
总结: 线程中断机制在整个并发编程中起着非常大的作用, 尤其和 LockSupport 配合使用
参考资料:
skywang12345 线程 interrupt
zhanjindong LockSupport和Interrupt (这篇写得相当好👌)
网友评论