彻底搞懂Java的等待-通知(wait-notify)机制
https://achang.blog.csdn.net/article/details/122790061
并发编程——线程中sleep(),yield(),join(),wait(),notify(),notifyAll()区别
先上一个代码例子再来理解,两个线程交替打印:
public class Test1 {
//共享对象,用来实现对象锁
static final Object object = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
synchronized (object) {
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行");
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1");
Thread t2 = new Thread(() -> {
while (true) {
synchronized (object) {
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行");
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t2");
t1.start();
t2.start();
}
}
运行结果:
t1执行
t2执行
t1执行
t2执行
t1执行
t2执行
t1执行
t2执行
t1执行
t2执行
t1执行
...
sleep()
- sleep是一个静态方法,它接受一个long类型的毫秒值参数,而且是一个本地方法(native修饰),而且会抛出InterruptedException(中断异常)。
- sleep()这个方法的使用很简单,直接Thread.sleep(毫秒值),休眠指定的毫秒数。使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会执行。但是时间到了之后线程会进入就绪队列,重新去竞争cpu资源。
- sleep()会释放cpu资源,但是不会释放同步锁(类锁和对象锁)
什么叫释放cpu资源呢?解释如下:
例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
yield()
使当前正在执行的线程向另一个线程交出运行权。注意这是一个静态方法。
该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
1、yield()执行后线程直接进入就绪状态。
2、yield()会释放cpu资源,但是不会释放同步锁(类锁和对象锁)
join()
执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到A结束或中断线程B。
可以实现一个线程的顺序执行。
wait()
使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒
notify()
随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
notifyAll()
使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
sleep和wait的区别
- 当线程执行sleep方法时,不会释放当前的锁(如果当前线程进入了同步锁),也不会让出CPU。sleep(milliseconds)可以用指定时间使它自动唤醒过来,如果时间不到只能调用interrupt方法强行打断。
- 当线程执行wait方法时,会释放当前的锁,然后让出CPU,进入等待状态。只有当notify/notifyAll被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。
wait、notify/notifyAll和sleep的区别与联系
- 前三个方法是Object的本地final方法,sleep方法是Thead类的静态方法。
- wait使当前线程阻塞,前提是必须先获得锁,所以只能在synchronized锁范围内里使用wait、notify/notifyAll方法,而sleep可以在任何地方使用。
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
- notify和wait的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
notify和notifyAll的区别
- notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
- notifyAll会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。
锁池和等待池
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁。
notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。
yield()方法和sleep()方法有什么区别
- yield()方法调用后线程处于RUNNABLE状态,所以yield()方法调用后线程只是暂时的将调度权让给别人,但立刻可以回到竞争线程锁的状态
- sleep()方法调用后线程处于TIME_WAITING状态,sleep()方法调用后线程处于阻塞状态。
sleep()方法和wait()方法的异同点
- wait()、notify()方法必须写在同步方法中,是为了防止死锁和永久等待,使线程更安全,而sleep()方法不需要有这个限制。
- wait()方法调用后会释放锁sleep()方法调用后不会释放锁。
- sleep()方法必须要指定时间参数;wait()方法可以指定时间参数。
- 两个方法所属类不同,sleep()方法属于Thread类;wait()属于Object类中,放在Object类中是因为Java中每个类都可以是一把锁。