目录
1、线程状态
2、线程终止
2.1、interrupt()
2.2、使用volatile标志位
3、线程通信
3.1、等待/通知机制
3.2、suspend()/resume()
3.3、wait()/notify()
3.4、LockSupport.park()/LockSupport.unpark()
3.5、Thread.join()
线程是系统调度的最小单元,cpu会在多个线程之间进行上下文切换,多线程能够充分的利用cpu的运算能力,这点在多核cpu当中体现得更加明显。对于一个应用程序而言,操作系统会为其创建一个进程,同时在这个进程中存在多个线程,多个线程的交替执行使得程序能够正常工作。
在Thread的内部类State中,明确定义出Java线程的6个状态,并且指出这个状态只是jvm中的线程状态,而不是真正意义上的操作系统中的线程状态。
通过下图可以表示各个线程状态之间的关系(来自《Java并发编程的艺术》):
实例代码如下:
package com.xiaohuihui.thread;
/**
* @Desription: 多线程运行状态切换示例
* @Author: yangchenhui
*/
public class Demo2 {
public static void main(String[] args) throws Exception {
// 第一种状态切换 - 新建 -> 运行 -> 终止
System.out.println("#######第一种状态切换 - 新建 -> 运行 -> 终止################################");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1当前状态:" + Thread.currentThread().getState().toString());
System.out.println("thread1 执行了");
}
});
System.out.println("没调用start方法,thread1当前状态:" + thread1.getState().toString());
thread1.start();
// 等待thread1执行结束,再看状态
Thread.sleep(2000L);
System.out.println("等待两秒,再看thread1当前状态:" + thread1.getState().toString());
// thread1.start(); TODO 注意,线程终止之后,再进行调用,会抛出IllegalThreadStateException异常
System.out.println();
System.out.println("############第二种:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)###########################");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 将线程2移动到等待状态,1500后自动唤醒
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2当前状态:" + Thread.currentThread().getState().toString());
System.out.println("thread2 执行了");
}
});
System.out.println("没调用start方法,thread2当前状态:" + thread2.getState().toString());
thread2.start();
System.out.println("调用start方法,thread2当前状态:" + thread2.getState().toString());
// 等待200毫秒,再看状态
Thread.sleep(200L);
System.out.println("等待200毫秒,再看thread2当前状态:" + thread2.getState().toString());
// 再等待3秒,让thread2执行完毕,再看状态
Thread.sleep(3000L);
System.out.println("等待3秒,再看thread2当前状态:" + thread2.getState().toString());
System.out.println();
System.out.println("############第三种:新建 -> 运行 -> 阻塞 -> 运行 -> 终止###########################");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Demo2.class) {
System.out.println("thread3当前状态:" + Thread.currentThread().getState().toString());
System.out.println("thread3 执行了");
}
}
});
synchronized (Demo2.class) {
System.out.println("没调用start方法,thread3当前状态:" + thread3.getState().toString());
thread3.start();
System.out.println("调用start方法,thread3当前状态:" + thread3.getState().toString());
// 等待200毫秒,再看状态
Thread.sleep(200L);
System.out.println("等待200毫秒,再看thread3当前状态:" + thread3.getState().toString());
}
// 再等待3秒,让thread3执行完毕,再看状态
Thread.sleep(3000L);
System.out.println("等待3秒,让thread3抢到锁,再看thread3当前状态:" + thread3.getState().toString());
}
}
在Java中,我们可以调用thread.start()告诉jvm当前线程处于就绪状态,等待操作系统的执行调用,那么我们又如何终止一个线程呢?一般有两种方式可以终止线程。
Thread类中的stop()和interrupt()都可以终止线程,但是stop()是一个过期的方法,因为stop在执行的过程中会释放掉锁,可能会造成线程安全问题,所以推荐使用interrupt()方法。
interrupt()在执行的过程中有可能会抛出异常,在源码注释中有写到wait(long),join(long),sleep(long)可能会抛InterruptedException。
下面通过一段代码可以验证stop的安全性问题:
package com.xiaohuihui.thread;
/**
* @Desription: 线程stop强制性中止,破坏线程安全的示例
* @Author: yangchenhui
*/
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒,确保i变量自增成功
Thread.sleep(1000);
// 暂停线程
// thread.stop(); // 错误的终止
thread.interrupt(); // 正确终止
while (thread.isAlive()) {
// 确保线程已经终止
} // 输出结果
thread.print();
}
}
package com.xiaohuihui.thread;
/**
* @Desription:
* @Author: yangchenhui
*/
public class StopThread extends Thread {
private int i = 0, j = 0;
@Override
public void run() {
synchronized (this) {
// 增加同步锁,确保线程安全
++i;
try {
// 休眠10秒,模拟耗时操作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++j;
}
}
/**
* 打印i和j
*/
public void print() {
System.out.println("i=" + i + " j=" + j);
}
}
利用volatile的可见性特性,也可以实现逻辑上的线程终止,代码如下:
package com.xiaohuihui.thread;
/**
* @Desription: 使用volatile变量实现线程终止
* @Author: yangchenhui
*/
public class Demo4 {
public volatile static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
System.out.println("运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(3000);
flag = false;
System.out.println("程序运行结束");
}
}
线程开始运行之后,会拥有线程独有的空间,同时也会有线程共享的空间。虽然Java对象和成员变量是存在于堆内存中,属于共享变量,但是每个线程依然可以拥有一份拷贝到自己的私有空间,这样做的目的是提高程序运行的性能。所以如果线程间不能通信,那么也就无法感知到共享变量的变化,无法更新私有空间中的变量值。
线程间的通信就是为了解决共享变量的可见性问题,之前博客中的volatile和synchronized就是线程通信的一种方式。
在我们来看其他的线程通信方式之前,我们先了解什么是等待/通知机制(生产者/消费者模型)。
其实Demo4就使用到了这一编程思想,通过一个被volatile修饰的公共变量,来进行两个线程之间的通信。一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者就是消费者。
可以抽象为下面的伪代码:
while(!flag){
thread.sleep(1000);
}
doSomething();
但是上面的代码存在一定的问题,如果休眠的时间过长,那么消息无法及时的进行处理,但是如果休眠时间太短,又存在过多消耗cpu的情况。使用Java的等待通知机制,能够解决这个问题。
将上述模式再抽取为等待/通知的经典范式(《Java编发编程的艺术》):
1、获取对象的锁。
2、如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3、条件满足则执行对应的逻辑。
synchronized(object){
while(条件不满足){
object.wait();
}
doSomething();
}
JDK推荐使用while方式判断条件,不推荐使用if,因为notify()、notifyAll()和unpark()在执行的过程中可能会存在伪唤醒的情况。
1、获得对象的锁。
2、改变条件。
3、通知所有等待在对象上的线程。
synchronized(object){
改变条件
object.notifyAll();
}
使用suspend()/resume();wait()/notify();LockSupport.park()/LockSupport.unpark()可以进行线程之间的通信,suspend()/resume()会引发线程死锁问题,是jdk的过期方法,不推荐使用。
thread.suspend()会挂起线程,但是在这个过程中并不会释放锁,所以容易造成线程死锁。如果thread.resume()先于thread.suspend()也会导致线程被永久挂起。
wait()/notify()通过对象监视器实现,所以这两个方法都需要在同步代码块里面进行调用。如果不在同步代码块里面,会抛出IllegalMonitorStateException异常。跟suspend()/resume()一样,wait()/notify()也会有顺序要求,如果notify()在wait()之前,并且wait()没有传超时时间,那么wait的线程就会一直处于WAITING状态。调用wait()方法的时候会释放对象锁,所以notify()执行的时候才能够获取到对象锁。
package com.xiaohuihui.lock;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Desription:
* @Author: yangchenhui
*/
public class WaitNotifyDemo {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
// 加锁
synchronized (lock) {
// 当条件不满足时,继续wait,同时释放lock的锁
while (flag) {
System.out.println(Thread.currentThread() + " flag_is_true.wait@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 条件满足时完成工作
System.out.println(Thread.currentThread() + " flag_is_false.wait@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 获取lock的锁,然后进行通知,通知的时候不会释放lock的锁
// 直到当前线程释放了lock后,WaitThread才能够从wait()方法中返回
System.out.println(Thread.currentThread() + " hold_lock.notify@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold_lock_again.sleep@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
不管是suspend()/resume(),还是wait()/notify(),都会存在调用的先后顺序问题。但LockSupport.park()/LockSupport.unpark()不会存在先后顺序问题。在调用park的时候并不会释放对象锁,只是将当前线程挂起,如果是在同步代码块当中调用park方法,然后又使用同样的锁,再调用unpark(Thread thread)的时候就有可能进入死锁状态。
调用多次unpark()只会生效一次,当调用过一次park()之后,状态位就会被还原。park()和unpark()只是在操作状态位而已。
在Java 6中,LockSupport增 加 了park(Object blocker)、parkNanos(Object blocker, longnanos)和parkUntil(Object blocker, long deadline)3个方法,用于实现阻塞当前线程的功能,其中参数blocker是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题排查和系统监控。
三种线程通信方式的示例代码如下:
package com.xiaohuihui.thread;
import java.util.concurrent.locks.LockSupport;
/**
* @Desription: 三种线程协作通信的方式:suspend/resume、wait/notify、park/unpark
* @Author: yangchenhui
*/
public class Demo5 {
public static Object pizza = null;
public static void main(String[] args) throws Exception {
// 对调用顺序有要求,也要开发自己注意锁的释放。这个被弃用的API, 容易死锁,也容易导致永久挂起。
// new Demo5().suspendResumeTest();
// new Demo5().suspendResumeDeadLockTest();
// new Demo5().suspendResumeDeadLockTest2();
// wait/notify要求在同步关键字里面使用,免去了死锁的困扰,但是一定要先调用wait,再调用notify,否则永久等待了
// new Demo5().waitNotifyTest();
// new Demo5().waitNotifyDeadLockTest();
// park/unpark没有顺序要求,但是park并不会释放锁,所以在同步代码中使用要注意
// new Demo5().parkUnparkTest();
new Demo5().parkUnparkDeadLockTest();
}
/**
* 正常的suspend/resume
*/
public void suspendResumeTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (pizza == null) {
// 如果没包子,则进入等待
System.out.println("1、进入等待");
Thread.currentThread().suspend();
}
System.out.println("2、买到pizza,回家");
});
consumerThread.start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
}
/**
* 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码
*/
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (pizza == null) {
// 如果没pizza,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、买到pizza,回家");
});
consumerThread.start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
/**
* 导致程序永久挂起的suspend/resume
*/
public void suspendResumeDeadLockTest2() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (pizza == null) {
System.out.println("1、没pizza,进入等待");
try {
// 为这个线程加上一点延时
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、买到pizza,回家");
});
consumerThread.start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
consumerThread.join();
}
/**
* 正常的wait/notify
*/
public void waitNotifyTest() throws Exception {
this.wait();
// 启动线程
new Thread(() -> {
synchronized (this) {
while (pizza == null) {
// 如果没pizza,则进入等待
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到pizza,回家");
}).start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
/**
* 会导致程序永久等待的wait/notify
*/
public void waitNotifyDeadLockTest() throws Exception {
// 启动线程
new Thread(() -> {
if (pizza == null) {
// 如果没pizza,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到pizza,回家");
}).start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
/**
* 正常的park/unpark
*/
public void parkUnparkTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (pizza == null) {
// 如果没pizza,则进入等待
System.out.println("1、进入等待");
LockSupport.park();
}
System.out.println("2、买到pizza,回家");
});
consumerThread.start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
/**
* 死锁的park/unpark
*/
public void parkUnparkDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (pizza == null) {
// 如果没pizza,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到pizza,回家");
});
consumerThread.start();
// 3秒之后,生产一个pizza
Thread.sleep(3000L);
pizza = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}
}
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(longmillis, int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。
看看thread.join()源码如下:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* interrupted status of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
由源码可以看到thread.join()也是基于wait()/notify()实现,当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。下面为Thread.join()的示例代码:
package com.xiaohuihui.lock;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Desription:
* @Author: yangchenhui
*/
public class WaitNotifyDemo {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
// 加锁
synchronized (lock) {
// 当条件不满足时,继续wait,同时释放lock的锁
while (flag) {
System.out.println(Thread.currentThread() + " flag_is_true.wait@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 条件满足时完成工作
System.out.println(Thread.currentThread() + " flag_is_false.wait@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 获取lock的锁,然后进行通知,通知的时候不会释放lock的锁
// 直到当前线程释放了lock后,WaitThread才能够从wait()方法中返回
System.out.println(Thread.currentThread() + " hold_lock.notify@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold_lock_again.sleep@ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
(PS:实现原理待后续博客更新)