Java之synchronized(), wait()和notify() 详细案例解析

Object类中提供了三个final方法, 无法重写和继承, 这三个方法都必须在同步代码块中执行(例如synchronized块):

wait(): 等待

notify(): 唤醒单个线程(随机一个)

notifyAll(): 唤醒所有线程

wait(): 通过锁对象来调用, “synchronized(a) {a.wait(); }”. 本质是线程调用方法后, 暂时让出同步锁(解锁), 以便其他线程能进入同步锁, 并进入等待阻塞状态.

notify() / notifyAll(): 通过锁对象调用, “synchronized(a) {a.notify(); }”. 本质是通知其他处于等待阻塞的线程, 可以开始和其他线程抢同步锁, 首先进入同步阻塞状态, 如果抢到锁, 则进入就绪状态, 获得程序执行权. 这两个方法通知的是通过该对象同步锁的线程, notify()是通知随机一个, notifyAll()是通知所有.

特别注意: notify() / notifyAll()并不代表线程可以马上开始执行,仍然需要和其他线程抢锁
在这里插入图片描述
如下生产消费案例, SetStudent类来生产, GetStudent类来消费输出.

先做一个学生类

public class Student {
    String name;
    int age;
}

生产类

public class SetStudent implements Runnable {
    private Student s;
    private int x = 0;

    public SetStudent() {}
	//s = AA;
    public SetStudent(Student s) {this.s = s;}

    @Override
    public void run() {
        while (true) {
            //锁对象s和下面的ss是同一个对象
            synchronized (s) {
                if (x % 3 == 0) {
                    s.name = "aaaaaa"; s.age = 25;
                } else if(x%3==1){
                    s.name = "bbbbbb"; s.age = 98;
                } else{
                    s.name = "qqqqqq"; s.age = 111;
                }
                x++;
            }
            if(x==30){ System.exit(0); }
        }
    }
}

消费类

public class GetStudent implements Runnable {
    private Student ss;

    public GetStudent() { }
	//ss = AA;
    public GetStudent(Student s) { this.ss = s; }

    @Override
    public void run() {
        while (true) {
            synchronized (ss) {
                System.out.println(ss.name + "==" + ss.age);
            }
        }
    }
}

通过生产类依次给Student设置不同的属性, 通过消费类将Student打印输出.

其中生产和消费类的Synchronized锁对象必须一样, 不能用this, 案例中的s/ss, 其实是同一把锁, 将测试类的AA学生传递到构造中赋值给到s和ss, 这样才能保证生产学生和消费打印不会同时进行, 要么set, 要么get.

最后贴上测试类

public class StudentDemo {
    public static void main(String[] args) {
        Student AA = new Student();//AA是用来区分线程中的变量
        GetStudent gt = new GetStudent(AA);
        SetStudent st = new SetStudent(AA);

        Thread th1 = new Thread(st);
        Thread th2 = new Thread(gt);
        th2.start();
        th1.start();
    }
}

打印结果

null0
null
0
bbbbbb98
bbbbbb
98
aaaaaa25
aaaaaa
25
aaaaaa25
aaaaaa
25

代码问题:

  1. GetStudent在学生还没有set时就先抢到线程运行权, 输出空值.

    增加判断标记, GetStudent线程获取monitor锁后判断, 如果没有学生则wait(), 只有在学生设置好后, 接到SetStudent的 notify()通知, 然后再运行输出语句, 并置换flag状态, 然后notify(), 避免死锁.

  2. 同一个学生连续打印多次, 没有依次打印.

    增加判断标记flag, 学生如果没有设置, 则设置学生, 然后置换flag状态, 并notify() 同步锁中处于等待阻塞的线程GetStudent, 告诉他, 有学生了可以开始消费了. 如果设置的学生还未消费, 则wait(), 直至消费后被唤醒.

修改优化代码如下:

学生类

public class Student {
    String name;
    int age;
    boolean flag; //增加flag, 标记学生是否已经生产或者消费
}

生产类

public class SetStudent implements Runnable {
    private Student s;
    private int x = 0;

    public SetStudent() { }

    public SetStudent(Student s) { this.s = s; }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (s.flag) {
                    try {
                        //如果学生还未消费,则等待
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (x % 3 == 0) {
                    s.name = "aaaaaa"; s.age = 25;
                } else if(x%3==1){
                    s.name = "bbbbbb"; s.age = 98;
                } else{
                    s.name = "qqqqqq"; s.age = 111;
                }
                x++;
                s.flag = true;//标记学生已经生产完毕
                s.notify();
            }
            if(x==30){
                System.exit(0);
            }
        }
    }
}

消费类

public class GetStudent implements Runnable {
    private Student ss;

    public GetStudent() {
    }

    public GetStudent(Student s) {
        this.ss = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (ss) {
                //如果学生还未生产,则等待
                if(!ss.flag){
                    try {
                        ss.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(ss.name + "==" + ss.age);
                ss.flag=false;//标记学生已经消费完毕
                ss.notify();
            }
        }
    }
}

结果打印
在这里插入图片描述
总结:

  1. wait() 和notify()需要在同步代码中执行, 由锁对象调用
  2. wait()会释放同步锁, 并进入阻塞状态.
  3. notify()只是通知等待线程, 可以重新抢monitor锁

猜你喜欢

转载自blog.csdn.net/weixin_42098099/article/details/84875255