(十二)yield、notify、notifyAll、sleep、join、wait的区别
线程之间是可以进行通信的,线程提供了很多方法,比如wait、notify 、sleep等等。
这篇文章来介绍一下这些方法的使用以及区别。
# 1. wait ,notify / notifyAll
核心源码:
public final void wait() throws InterruptedException {
wait(0);
}
Object.wait(long)
要跟Object.notify()/notifyAll()
搭配使用。
wait 与 notify/notifyAll 方法必须在synchronized 同步代码块中使用,即要先对调用对象加锁,不放在synchronized 中则会在program runtime时会抛出“java.lang.IllegalMonitorStateException”
异常。
所以wait方法要在加锁的情况下使用
如果指定了wait的时间,到时间会自动唤醒;否则就需要 notify / notifyAll 去唤醒。
notify() 和 notifyAll() 有什么区别?
notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
# 2. yield
yield()方法作用是放弃当前CPU资源,让给其他线程去使用,但是放弃时间不确定。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁,由运行状态变为就绪状态, 让OS再次选择线程,但yield()从未导致线程转到等待/睡眠/阻塞状态。
作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
# 3.join
thread.join()/thread.join(long millis),假如 当前线程里调用其它线程 t 的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
join的核心源码是这个:
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
就是说调用join方法的线程,其他线程为其让步,让它先执行完才能执行,阻塞主线程,直到被唤醒。
demo:
public class ThreadMethodTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread("Thread 1");
MyThread myThread2 = new MyThread("Thread 2");
myThread1.start();
myThread1.join();
myThread2.start();
myThread2.join();
System.out.println(Thread.currentThread().getName()+ " 线程正在运行");
}
static class MyThread extends Thread{
MyThread(String name){
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程正在运行");
}
}
}
输出:
Thread 1 线程正在运行
Thread 2 线程正在运行
main 线程正在运行
myThread1.start() 后 再 myThread1.join() 这样的话,myThread1执行完才会执行 myThread2.start()。
如果改成:
public class ThreadMethodTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread("Thread 1");
MyThread myThread2 = new MyThread("Thread 2");
myThread1.start();
myThread2.start();
myThread2.join();
myThread1.join();
System.out.println(Thread.currentThread().getName()+ " 线程正在运行");
}
static class MyThread extends Thread{
MyThread(String name){
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程正在运行");
}
}
}
输出:
Thread 1 线程正在运行
Thread 2 线程正在运行
main 线程正在运行
main 线程必须等待,但是 Thread 1、Thread 2 都已经 start()了,调用join的先后顺序不影响,都是并行的。
# 4. sleep
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,释放CPU,millis后线程自动苏醒进入就绪状态。
作用:给其它线程执行机会的最佳方式。
有时候会使用sleep(0)使CPU重新发起一次调度。
# wait 和 sleep的区别
- wait()和sleep()都可以通过interrupt()方法立即 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
- wait() 是Object类 的方法,sleep()是 Thread 类的方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
- 都可以指定阻塞指定秒数,并返回。
- sleep方法没有释放锁,而wait方法释放了锁。
# 5.守护线程
使用多线程的时候,如果有一个子线程没有结束,jvm进程就不会退出,为了确保所有线程都能够结束,就引入了守护线程。
用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。 守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。 setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,false为User模式。
demo:
public class DaemonDemo {
public static void main(String[] args) {
Thread daemonThread1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println(Thread.currentThread().getName() + " 子线程执行中....");
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finally 语句块");
}
}
}
}, "daemonThread1");
Thread daemonThread2 = new Thread(new Runnable() {
int i = 2;
@Override
public void run() {
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " 子线程执行中....");
}
}
}, "daemonThread2");
daemonThread1.setDaemon(true); //daemon要先于start前执行,否则会报 IllegalThreadStateException
daemonThread1.start();
daemonThread2.start();
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("daemonThread1 状态"+daemonThread1.isAlive());
System.out.println("daemonThread2 状态"+daemonThread2.isAlive());
}
}
输出:
daemonThread1 子线程执行中....
daemonThread2 子线程执行中....
daemonThread2 子线程执行中....
daemonThread1 finally 语句块
daemonThread1 子线程执行中....
daemonThread1 finally 语句块
daemonThread1 子线程执行中....
daemonThread1 状态true
daemonThread2 状态false
上述有2个子线程daemonThread1、daemonThread2,我给daemonThread1设置为守护线程,一直是无限循环,当两个线程执行的时候,随着daemonThread2执行结束、还有main线程结束,之后显示daemonThread1还是alive的,但是因为daemonThread1设置为守护线程,有且只有守护线程的时候,jvm退出。
结论:
- 当线程只剩下守护线程的时候,JVM就会退出;补充一点如果还有其他的任意一个用户线程还在,JVM就不会退出。.
- 守护线程结束的时候,不会执行finally的语句块。
- setDaemon要先于start()前执行,否则会报 IllegalThreadStateException。
参考: