JavaEE 第7节 线程饥饿及其解决办法

目录

一、什么是线程饥饿?

二、线程饥饿的解决办法

*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方法上,且是原子性的!(最后一个小节讲解原因)

使用方式:

java 复制代码
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线程运行完毕,进而造成程序的卡死,注意这不是死锁!

如果不想死等,那么可以使用带时间参数的重载方法:

java 复制代码
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而睡眠的线程唤醒:

java 复制代码
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方法会"随机"的唤醒一个线程(具体由操作系统决定):

java 复制代码
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过:

java 复制代码
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方法:

java 复制代码
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)。

相关推荐
P.H. Infinity36 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天40 分钟前
java的threadlocal为何内存泄漏
java
caridle1 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
萧鼎1 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
学地理的小胖砸1 小时前
【一些关于Python的信息和帮助】
开发语言·python
疯一样的码农1 小时前
Python 继承、多态、封装、抽象
开发语言·python
^velpro^1 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋31 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花1 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端1 小时前
第六章 7.0 LinkList
java·开发语言·网络