【多线程安全】线程的调度顺序 wait和notify

我们都知道多线程的调度顺序是随机的,系统随机调度,抢占式执行,很多时候,也是希望能够通过一定的手段进行顺序执行的。 

比如可以用join方法进行阻塞等待。也能达到线程之间顺序执行。但是使用join方法,只能等调用方法的线程结束,我们所希望的是一种既能实现线程之间的顺序执行,也能让调用线程不结束。即使用wait方法。

wait

执行时有三步:

  1. 让线程进入阻塞等待。(把线程放到阻塞队列中)
  2. 释放当前的锁。
  3. 当线程被唤醒时,重新获取到锁。

代码演示:

public class test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        object.wait();
    }
}

结果:

代码报错,报错原因是非法的监视器状态异常。这是因为使用wait方法的前提是线程要处于加锁状态,否则会报错的。即就是要加synchronized,它也被称为监视器。接下来代码加上,预期效果是输出线程结束!。

代码演示:

public class test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            object.wait();
        }
        System.out.println("线程结束!");

    }
}

上面代码会进入一个线程阻塞等待。直到被唤醒。因此还要加上notify。注意notify还是要包含在synchronized里的。

public class test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (object) {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1线程结束!");
            }

        });

        Thread t2 = new Thread(() -> {
            synchronized (object) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                object.notify();
            }
        });

        t1.start();
        t2.start();

    }
}

结果:

总结:

  • 使用wait方法的前提是该线程要处于加锁状态,否则会抛异常。即要搭配synchronized使用。
  • wait等的状态是阻塞等待,会把线程放到阻塞队列中,不会占据CPU。要使用notify方法去唤醒。
  • wait结束等待的条件是:其他线程调用该对象的notify方法。
  • wait,notify,notifyAll都是Object的方法。
  • wait和notify需要借助同一个对象,并搭配synchronized。

notifyAll()  和 wait(long timeout) 

  • notify是一次唤醒一个线程,而notifyAll是一次唤醒全部线程。由于有时候调用wait不一定只有一个线程调用。于是唤醒也有两种方法。但相比之下,notify更可控。用的多。注意唤醒的时候,wait要涉及到一个重新获取锁的过程。也是需要串行执行的。
  • wait(long timeout)指定等待时间,避免wait无休止的等待下去。

wait和join的区别?

wait会让当前线程进入阻塞等待状态,并释放锁,但不会使调用的线程结束后才继续执行,而是在其他线程中调用notify唤醒当前阻塞的线程,拿到锁后,再继续执行。但调用join方法是等调用join方法的线程执行结束后,再执行其他线程,不涉及所相关的操作。wait必须在synchronized修饰的代码块或方法中使用,join方法可以在任何位置使用。

wait和sleep的区别?

共同点:

  • 都是使线程暂停一段时间的方法。

不同点:

  1. wait是Object类的一个方法,sleep是Thread类的一个方法。
  2. wait必须在synchronized修饰的代码块或方法中使用,sleep方法可以在任何位置使用。
  3. wait被调用后当前线程进入BLOCK状态并释放锁,并可以通过notify或notifyAll方法进行唤醒;而sleep被调用后当前线程进入TIME_WAIT状态,不涉及锁相关的操作。

什么是线程饿死?

  • 定义:线程饿死是指一个或多个线程由于某种原因无法获取所需的资源或执行机会,导致无法继续正常执行,从而被阻塞在某个状态。不能完成其任务。这种情况通常是由于资源竞争或优先级设置不当造成的。
  • 特征:在CPU上执行的线程没有主动放弃CPU,或因某种原因,在CPU上执行完后,又反复上CPU执行,导致其他线程处于长时间阻塞等待。
  • 示例:一个线程池中的任务都设置了较高的优先级,导致优先级低的任务一直到得不到执行机会,从饿死。

线程饥饿的可能原因

  1. 可能是不同线程或线程组之间的线程优先级不正确。
  2. 可能是使用非终止循环(无限循环)或在特定资源上等待过多时间,同时保留了其他线程所需的关键锁。

解决办法

避免饿死就应该是采用队列的方式,保证每个人都有机会获得请求的资源。 当然实现方式可以很多个变化,比如优先级,时间片,等,都是“队列”的特殊形式。

什么是活锁?

活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

活锁指的是任务或执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败。活锁和死锁的区别在于,处于活锁的实体在不断的改变状态,而处于死锁的实体表现为等待,活锁有可能自行解开,死锁则不能。
 

关于死锁与活锁的形象比喻

死锁:迎面开来的汽车A和汽车B过马路,汽车A得到了半条路的资源(满足死锁发生条件1:资源访问是排他性的,我占了路你就不能上来,除非你爬我头上去),汽车B占了汽车A的另外半条路的资源,A想过去必须请求另一半被B占用的道路(死锁发生条件2:必须整条车身的空间才能开过去,我已经占了一半,尼玛另一半的路被B占用了),B若想过去也必须等待A让路,A是辆兰博基尼,B是开奇瑞QQ的屌丝,A素质比较低开窗对B狂骂:快给老子让开,B很生气,你妈逼的,老子就不让(死锁发生条件3:在未使用完资源前,不能被其他线程剥夺),于是两者相互僵持一个都走不了(死锁发生条件4:环路等待条件),而且导致整条道上的后续车辆也走不了。

活锁:马路中间有条小桥,只能容纳一辆车经过,桥两头开来两辆车A和B,A比较礼貌,示意B先过,B也比较礼貌,示意A先过,结果两人一直谦让谁也过不去。
 

你可能感兴趣的:(习题总结,多线程安全,java,开发语言,线程调度)