目录
一、什么是线程饥饿?
二、线程饥饿的解决办法
*wait()与notify()方法解决线程饥饿
1、wait(等待)
2、notify(通知)
1)notify
2)notifyAll
3)关于wait方法的一些补充
1、wait的方法的三个功能是原子性的:
2、sleep与wait方法的异同
相同点:
不同点:
所属类:
锁释放:
使用场景:
调用位置:
一、什么是线程饥饿?
线程饥饿(Thread Starvation)指的是多线程中,某些线程无法获得相关资源或者执行机会(阻塞,BLOCKED),长时间如此对线程的推进和响应造成明显影响的现象:
左边的蘑菇头和右边的小人都是线程,左边的蘑菇头因为在所中没有自己想要的资源所以从锁中出来,但是由于它的优先级比较高,一从锁里出来,转头有跑进锁里面了,完全没有给右边的小进去的机会。
当饥饿到一定程度,赋予线程的任务即使完成也不在具有实际意义的时候,就说明这个线程被饿死了。
二、线程饥饿的解决办法
线程饥饿原因:
线程产生饥饿,主要是因为系统对线程调度方式不够公平或者不合理导致的。
想要解决饥饿问题,我们只需要通过一些手段,对线程的调度进行合理的干预即可。
比如上图中的蘑菇头线程,如果自己目前拿不到想要的资源结果,那就先把锁让给别的线程,不要自己在锁这里反复横跳!等别的线程把资源变量改成蘑菇头想要的结果的时候,然后再让蘑菇头进来拿锁。
*wait()与notify()方法解决线程饥饿
这两个方法可以让多个线程按照某一个逻辑上的先后顺序执行,从而避免线程的饿死。
注意:两个方法都必须使用锁对象调用!
1、wait(等待)
wait()方法有三个功能:
1)让当前线程释放持有的锁。
2)让当前线程进入WAITING状态(或者TIMED_WAITING,取决于括号中是否含有时间参数,ms)。
3)检测其他线程是否调用了同对象的notify()方法,如果调用了notify()方法,那么唤醒正在睡眠的线程。
注释:
这三个功能都已经集成到的wait方法上,且是原子性的!(最后一个小节讲解原因)
使用方式:
public class Threads {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
synchronized (lock){
try {
lock.wait();//进入等待状态(WAITING),必须用lock调用
} catch (InterruptedException e) {//与sleep方法类似,需要try catch,
// 但是wait不能自动唤醒自己,只能考notify方法
e.printStackTrace();
}
}
});
thread1.start();
thread1.join();
}
}
上面这个程序会造成程序的卡死。
原因:
wait()没有时间参数,那么就必须等其他线程用notify()方法唤醒它,但是我没有写notify方法,所以thread1会一直处在WAITINGZ状态,但是主线程有用了join()方法,所以主线程一直在等thread1线程运行完毕,进而造成程序的卡死,注意这不是死锁!
如果不想死等,那么可以使用带时间参数的重载方法:
public class Threads {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
synchronized (lock){
try {
lock.wait(1000);//带参数,1秒
} catch (InterruptedException e) {//与sleep方法类似,需要try catch
e.printStackTrace();
}
}
});
thread1.start();
thread1.join();
}
}
2、notify(通知)
1)notify
我们刚才看到了,仅仅用wait方法是不够的,需要与notify方法搭配。
notify方法可以把因为wait而睡眠的线程唤醒:
public class Threads {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
synchronized (lock){
try {
System.out.println("thread1即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread1成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2=new Thread(()->{
synchronized (lock){
try {
Thread.sleep(1000);//确保thread1线程先执行,不然唤醒个寂寞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2即将使用norify方法唤醒thread1");
lock.notify();//同样,必须用lock调用!!
System.out.println("thread2已唤醒thread1");
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
执行结果:
倘若同时有多个线程调用wait方法,那么notify方法会"随机"的唤醒一个线程(具体由操作系统决定):
public class Threads {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("thread1即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread1成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("thread2即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread2成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("thread3即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread3成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread4 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(1000);//确保thread1线程先执行,不然唤醒个寂寞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread4即将使用norify方法唤醒thread1");
lock.notify();
System.out.println("thread2已唤醒thread1");
}
});
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
}
}
执行结果只会唤醒一个线程:
注意:
多次notify不会有副作用,即使没有线程wait过:
public class Threads { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(()->{ synchronized (lock){ lock.notify(); lock.notify(); lock.notify(); lock.notify(); lock.notify(); lock.notify(); lock.notify(); lock.notify(); lock.notify(); } }); t1.start(); t1.join(); } }
当然不建议这样做,谁会这么无聊🤪
2)notifyAll
如果想要全部唤醒,也有办法,那就是使用notifyAll方法:
public class Threads {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("thread1即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread1成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("thread2即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread2成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("thread3即将进入睡眠,等待其他线程用notify方法唤醒");
lock.wait();
System.out.println("thread3成功被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread4 = new Thread(() -> {
try {
Thread.sleep(1000);//确保其他线程先执行,不然唤醒个寂寞
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock){
System.out.println("即将唤醒所有线程");
lock.notifyAll();/**换成notifyAll方法,其他代码都没有变*/
System.out.println("thread2已唤醒所有线程");
}
});
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
}
}
其中一个执行结果:
注意:
1)由于thread1、thread2、thread3是同时被唤醒的。之后这3个线程会进入锁竞争只有一个线程可以拿到锁然后执行,因此thread1、thread2、thread3三个线程那个先执行完是不确定的。
2)notify方法不像wait方法,wait方法调用的时候会释放当前对象的锁,但是notify方法没有这样的功能!!!也就是说,只有调用notify的线程释放了锁,被notify唤醒的线程才有机会执行。(notifyAll与notify是一样的)
3)关于wait方法的一些补充
1、wait的方法的三个功能是原子性的:
1)让当前线程释放持有的锁。
2)让当前线程进入WAITING状态(或者TIMED_WAITING,取决于括号中是否含有时间参数,ms)。
3)检测其他线程是否调用了同对象的notify()方法,如果调用了notify()方法,那么唤醒正在睡眠的线程。(InterruptedException也会提前唤醒wait过的线程)
假设不满足原子性:
有t1和t2两个线程,t1线程调用wait方法,要等待t2线程用notify唤醒t1。
若t1在执行完第一个功能后,由于线程调度的原因,t1在没有进入睡眠的状态就提前释放了锁,给t2,那么会出现这个情况:
t2线程提前执行了notify方法,t2线程运行完后,锁就交给了t1,紧接着t1就开始进入睡眠状态,等待t2线程用notify唤醒t1?这显然不符合逻辑。
2、sleep与wait方法的异同
相同点:
1)都可以暂停线程执行
2)都会抛出InterruptedException,需要对异常进行处理
注意:
调用了wait方法的线程如果捕获到InterruptedException,此线程就会终止。
不同点:
所属类:
1)sleep是Thread类的静态方法。
2)wait是Object类的示例方法,且必须由锁对象调用。
锁释放:
1)用睡眠不会释放锁,也就是抱着锁睡:
2)wait会释放锁,然后再睡。
使用场景:
1)sleep用于暂停线程执行一段时间,用的比较广泛。
2)wait一般用在线程之间的通信,等待某个条件成立,接收notify信号。
调用位置:
1)sleep可以在任意地方调用。
2)wait必须在同步块中调用(synchronized)。