Java 多线程死锁之谜以及等待唤醒机制(生产消费案例)

为了解决多线程的安全问题,我们用到了同步锁机制。但是当存在多个同步代码块嵌套时,可能出现死锁的现象。

何为死锁
这里写图片描述

如图:线程1拿着 A 锁,线程2拿着 B 锁。这个时候,线程1拿不到B锁,线程2拿不到A锁。就出现了僵持的情况,这就是死锁。

接下来我们看一下 线程的等待唤醒机制,在此之前,我们先明白一个概念——线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务,比如:一个生产,一个消费)却不相同。

等待唤醒机制就是通过一定的手段使各个线程能有序有效的利用资源。

等待唤醒机制所涉及到的方法:
wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步代码块 中才有效。同时这些方法在使用时必须标明所属锁(必须由锁对象 . 去调用),这样才可以明确出这些方法操作的到底是哪个锁上的线程。

仔细查看JavaAPI之后,发现这些方法 并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?

因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

下面举一个生产消费的一个案例:

//Resource.java
public class Resource {
    public String name;
    public String sex;
    /*
     * 用于标记生产消费模式的状态
     * flag=true 表示生产已经完成,等待被消费
     * flag=false 表示消费已经完成,等待生产
     * 默认是等待生产者生产
     */
    public boolean flag = false;
}
//InputThread.java
public class InputThread implements Runnable {

    private Resource r;
    private int count;

    public InputThread(Resource r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            // 因为生产者和消费者都使用的同一个资源,虽然执行的任务不同,所以要用相同的锁
            synchronized (r) {
                // 在生产之前,判断需不需要生产,如果不需要,就 r.wait()掉
                if (r.flag) {// flag=true,表示生产完成了,不需要生产
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (count % 2 == 0) {
                    r.name = "小花";
                    r.sex = "女";
                } else {
                    r.name = "Jack";
                    r.sex = "man";
                }
                // 生产完成后,要改变状态值,唤醒消费者。因为我们在生产前判断需不需要生产后,会wait(),所以这里不做wait()操作
                r.flag = true;
                r.notify();
            }
            count++;
        }
    }

}
//OutputThread.java
public class OutputThread implements Runnable {
    private Resource r;

    public OutputThread(Resource r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (r) {
                if (!r.flag) {
                    try {
                        r.wait();
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
                System.out.println(r.name + "......" + r.sex);
                r.flag = false;
                r.notify();
            }
        }
    }

}
//Test.java
public class Test {
    public static void main(String[] args) {
        Resource r = new Resource();

        InputThread in = new InputThread(r);
        OutputThread out = new OutputThread(r);

        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}
//输出结果
小花......女
Jack......man
小花......女
Jack......man
小花......女
Jack......man
小花......女
Jack......man
.
.
.

注意 sleep() 和 wait() 方法的区别:
1、sleep() 属于 Thread 类;wait() 属于 Object 类。
2、sleep() 不释放锁对象,释放 CPU 使用权。在休眠的时间内,不能被唤醒。
wait() 释放锁对象,释放 CPU 使用权。在等待时间内,可以被唤醒。

猜你喜欢

转载自blog.csdn.net/FresherHe/article/details/82694800
今日推荐