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锁
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.1 Java语言发展简史2 1.2 认识Java语言3 1.2.1 Java语言特性3 1.2.2 JavaApplet4 1.2.3 丰富的类库4 1.2.4 Java的竞争对手5 1.2.5 Java在应用领域的优势7 1.3 Java平台的体系结构7 1.3.1 JavaSE标准版8 1.3.2 JavaEE企业版10 1.3.3 JavaME微型版11 1.4 JavaSE环境安装和配置12 1.4.1 什么是JDK12 1.4.2 JDK安装目录和实用命令工具介绍12 1.4.3 设置环境变量13 1.4.4 验证配置的正确性14 1.5 MyEcilpse工具介绍JavaSE环境安装和配置15 1.6 本章练习16 第2章 2.1 什么是程序18 2.2 计算机中的程序18 2.3 Java程序19 2.3.1 Java程序中的类型19 2.3.2 Java程序开发三步曲21 2.3.3 开发Java第一个程序21 2.3.4 Java代码中的注释23 2.3.5 常见错误解析24 2.4 Java类库组织结构和文档27 2.5 Java虚拟机简介28 2.6 Java技术两种核心运行机制29 2.7 上机练习30 第3章 3.1 变量32 3.1.1 什么是变量32 3.1.2 为什么需要变量32 3.1.3 变量的声明和赋值33 3.1.4 变量应用实例33 3.2 数据的分类34 3.2.1 Java中的八种基本数据类型34 3.2.2 普及二进制36 3.2.3 进制间转换37 3.2.4 基本数据类型间转换38 3.2.5 数据类型应用实例38 3.2.6 引用数据类型39 3.3 关键字.标识符.常量39 3.3.1 变量命名规范39 3.3.2 经验之谈-常见错误的分析与处理40 3.3.3 Java标识符命名规则41 3.3.4 关键字42 3.3.5 常量42 3.4 运算符43 3.4.1 算术运算符43 3.4.2 赋值操作符45 3.4.3 关系操作符47 3.4.4 逻辑操作符48 3.4.5 位操作符49 3.4.6 移位运算符49 3.4.7 其他操作符50 3.5 表达式52 3.5.1 表达式简介52 3.5.2 表达式的类型和值52 3.5.3 表达式的运算顺序52 3.5.4 优先级和结合性问题52 3.6 选择结构54 3.6.1 顺序语句54 3.6.2 选择条件语句54 3.6.3 switch结构59 3.6.4 经验之谈-常见错误的分析与处理65 3.6.5 Switch和多重if结构比较66 3.7 循环语句66 3.7.1 While循环67 3.7.2 经验之谈-常见while错误70 3.7.3 do-while循环72 3.7.4 for循环74 3.7.5 经验之谈-for常见错误76 3.7.6 循环语句小结78 3.7.7 break语句79 3.7.8 continue语句82 3.8 JavaDebug技术84 3.9 本章练习85 第4章 4.1 一维数组90 4.1.1 为什么要使用数组90 4.1.2 什么是数组91 4.1.3 如何使用数组92 4.1.4 经验之谈-数组常见错误97 4.2 常用算法98 4.2.1 平均值,最大值,最小值98 4.2.3 数组排序102 4.2.3 数组复制103 4.3 多维数组105 4.3.1 二重循环105 4.3.2 控制流程进阶107 4.3.3 二维数组111 4.4 经典算法113 4.4.1 算法-冒泡排序113 4.4.2 插入排序115 4.5 增强for循环116 4.6 本章练习117 第5章 5.1 面向过程的设计思想120 5.2 面向对象的设计思想120 5.3 抽象121 5.3.1 对象的理解121 5.3.2 Java抽象思想的实现122 5.4 封装124 5.4.1 对象封装的概念理解124 5.4.2 类的理解125 5.4.3 Java类模板创建125 5.4.4 Java中对象的创建和使用127 5.5 属性130 5.5.1 属性的定义130 5.5.2 变量131 5.6 方法132 5.6.1 方法的定义132 5.6.2 构造方法135 5.6.4 方法重载138 5.6.5 自定义方法138 5.6.6 系统提供方法139 5.6.7 方法调用140 5.6.8 方法参数及其传递问题144 5.6.9 理解main方法语法及命令行参数147 5.6.1 0递归算法147 5.7 this关键字148 5.8 JavaBean149 5.9 包150 5.9.1 为什么需要包?150 5.9.2 如何创建包151 5.9.3 编译并生成包:151

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值