零基础学 Java
臧萌
PayPal 数据处理组技术负责人
46665 人已学习
新⼈⾸单¥68
课程目录
已完结/共 170 讲
第二章 Java面向对象编程 (74讲)
时长 07:13
时长 12:08
时长 06:26
时长 05:30
时长 14:16
时长 08:30
零基础学 Java
登录|注册
留言
29
收藏
沉浸
阅读
分享
手机端
回顶部
当前播放: 141 | 同步控制之wait notify
00:00 / 00:00
高清
  • 高清
1.0x
  • 2.0x
  • 1.5x
  • 1.25x
  • 1.0x
  • 0.75x
  • 0.5x
网页全屏
全屏
00:00
付费课程,可试看
01 | 课程介绍
02 | 内容综述
03 | 开发环境搭建(macOS)
04 | HelloWorld程序编译和运行(macOS)
05 | 开发环境搭建(Windows)
06 | HelloWorld程序编译和运行(Windows)
07 | 详解HelloWorld程序
08 | IntelliJ IDEA集成开发环境的安装和使用(macOS)
09 | IntelliJ IDEA集成开发环境的安装和使用(Windows)
10 | 从加减乘除到变量
11 | 再探计算加减乘除的程序
12 | Java中的基本数据类型
13 | Java中的运算符
14 | Java中的位运算符
15 | 基本数据类型的更多语法点
16 | 字符集编码和字符串
17 | 操作符和数据类型总结
18 | 程序执行流程之if-else语句(上)
19 | 程序执行流程之if-else语句(下)
20 | 程序循环之for语句
21 | 代码块和变量的作用域
22 | 程序循环之while语句
23 | 程序执行流程之switch语句
24 | 循环和判断的总结(上)
25 | 循环和判断的总结(下)
26 | 用数组保存成绩
27 | 认识变量和数组(上)
28 | 认识变量和数组(下)
29 | 多维数组
30 | 用数组灵活处理程序
31 | 类(class)
32 | 初探类和对象
33 | 认识引用类型(上)
34 | 认识引用类型(下)
35 | 类、对象和引用的关系
36 | 认识数组类型
37 | 引用的缺省值null
38 | 像自定义类型一样使用类
39 | Java中的包和访问修饰符(上)
40 | Java中的包和访问修饰符(下)
41 | 打造一个小超市
42 | IntelliJ调试程序初探
43 | 方法:让Merchandise对象有行为
44 | 返回值:让Merchandise计算毛利润
45 | 参数:让Merchandise计算多件商品的总价
46 | 参数和返回值是怎么传递的
47 | 分清参数、局部变量和实例的地盘
48 | 隐藏的this自引用
49 | 理解方法:一种特殊的代码块
50 | 理解方法的调用:代码的一种特殊跳转
51 | 给类和方法加Java注释
52 | 成熟的类的对象要自己做事情
53 | 方法的签名和重载
54 | 重载的参数匹配规则
55 | 构造方法:构造实例的方法
56 | 构造方法的重载和互相调用
57 | 静态变量
58 | 静态方法
59 | 静态方法的重载
60 | static代码块和static变量初始化
61 | 方法和属性的可见性修饰符
62 | 重新认识老朋友:Math和Scanner(上)
63 | 重新认识老朋友:Math和Scanner(下)
64 | 最熟悉的陌生人:String (上)
65 | 最熟悉的陌生人:String (下)
66 | 重新认识老朋友: main方法和System类
67 | String类的好兄弟
68 | 继承:方便让商品增加新的类别
69 | 子类对象里藏着一个父类对象
70 | 覆盖:子类想要一点不一样
71 | super:和父类对象沟通的桥梁
72 | super:调用父类的构造方法
73 | 父类和子类的引用赋值关系
74 | 多态:到底调用的哪个方法?(上)
75 | 多态:到底调用的哪个方法?(下)
76 | 多态里更多的语法点(上)
77 | 多态里更多的语法点(下)
78 | instanceof操作符
79 | 继承专属的访问控制:protected
80 | final修饰符(上)
81 | final修饰符(下)
82 | 继承里的静态方法
83 | 插曲:for循环的另一种写法
84 | 万类之祖:Object类
85 | hashCode和equals 方法(上)
86 | hashCode和equals 方法(下)
87 | toString方法
88 | 初探Class类
89 | 初探反射(上)
90 | 初探反射(下)
91 | 面向对象三要素:封装、继承和多态
92 | 枚举:定义商品的门类
93 | 接口:让商品类型更丰富(上)
94 | 接口:让商品类型更丰富(下)
95 | 抽象类:接口和类的混合体
96 | 有方法代码的接口
97 | 接口内代码的更多内容
98 | 静态内部类
99 | 成员内部类
100 | 局部内部类
101 | 匿名类
102 | 特殊类的总结
103 | 让我们的超市运转起来:设计篇
104 | 让我们的超市运转起来:代码篇
105 | 初识异常:try catch
106 | Java中异常的分类
107 | 抛出异常的语法
108 | Java异常的传递
109 | 自定义异常
110 | 异常传递不是凌波微步
111 | try catch finally语句
112 | 自动回收资源的try语句
113 | Java中的常见异常
114 | Collection类族简介
115 | Collection中的List (上)
116 | Collection中的List(下)
117 | Collection中的Set
118 | 泛型简析(上)
119 | 泛型简析(下)
120 | 再探泛型
121 | Iterator接口
122 | Map:key和value的映射
123 | 定义自己的注解
124 | Lambda V.S. 匿名类(上)
125 | Lambda V.S. 匿名类(下)
126 | 基本类型的自动装箱和拆箱
127 | Java中的File类
128 | Java I/O简介
129 | 写文件内容小程序
130 | 读文件内容小程序
131 | 网络通讯名词简介
132 | 简单的网络通讯小程序(上)
133 | 简单的网络通讯小程序(下)
134 | 简单的抓取网页内容的程序
135 | JDK和JRE
136 | 初识线程
137 | 创建自己的线程
138 | 再探线程
139 | 多线程:混乱开始了
140 | 同步控制之synchronized
141 | 同步控制之wait notify
142 | 多线程经典模型:生产者消费者
143 | 线程同步之join
144 | 死锁
145 | ThreadLocal线程专属的变量
146 | 定时任务
147 | volatile关键字的作用
148 | concurrent包基本原理
149 | concurrent包中的Atomic类族
150 | concurrent包中的锁
151 | concurrent包中的数据结构
152 | concurrent包中的线程池
153 | 聊天室开张喽 (上)
154 | 聊天室开张喽 (下)
155 | 什么是学习一门语言
156 | Java平台简介
157 | Maven概念简介
158 | Maven的安装和配置
159 | 创建一个简单的Maven项目
160 | 一个从pptx文件中抽取文字的小工具
161 | Maven常用命令和插件
162 | Intellij更多功能介绍
163 | 值得学习的类库简介
164 | 如何在Stack Overflow上提问才不会被骂
165 | 浅谈程序设计
166 | 游戏小程序功能定义
167 | 游戏小程序设计和模块划分
168 | 游戏小程序代码分析
169 | 使用Swagger创建一个Spring Boot的Web服务
170 | 结课测试&结束语
本节摘要

PDF 课件和源代码下载地址:
https://gitee.com/geektime-geekbang/LetsJava

登录 后留言

全部留言(29)

  • 最新
  • 精选
echo-coco
老师您好 有一个问题不是很清楚,希望能帮我解答一下。 既然说synchronized是不公平锁,那么对于这个样例程序里除了main以外的五个线程,4s左右,第二个thread结束sleep准备调用wait方法时,此时,main thread应该已经结束了3s的sleep,运行到notify的synchronized代码块,所以main thread 应该要和剩下的其余3个thread一起争抢locker的monitor,所以应该有可能出现main thread先抢到locker的monitor的情况,导致程序运行出错,但是我试了几次发现,程序总是在5个子线程都抢到locker的monitor之后,main thread才能拿到locker的monitor依次唤醒5个子线程,这是为什么呢?

作者回复: 是这样的,synchronized不保证(强调不保证)是公平锁。但是并不代表实际在某个具体程序的某次运行时,他的表现是和公平锁一样的。 只要有不公平的可能,就不能说是公平锁。要能百分之百保证在任何情况下都是公平锁,代价很高。synchronized是随缘公平,不保证公平。

2019-10-11
2
9
蓦然回首
再发一下notify的总结: 1、notify使用的前提: 调用notify的线程必须拥有指定object的monitor(锁) This method should only be called by a thread that is the owner of this object's monitor. 2、notify的作用: 唤醒正在指定object wait的单个线程。 如果有多个线程在该对象上等待,则选择其中一个唤醒。 该选择是任意的,并且可以根据实现来决定。 当前线程放指定object的monitor(锁)之前,被唤醒的线程将无法继续。 被唤醒的线程将和任何需要获得指定object的monitor(锁)的线程竞争。 被唤醒的线程在作为获得指定object的monitor(锁)的下一个线程时没有任何优势。

作者回复: 赞!

2020-11-18
4
song
老师,需要保证在所有线程进入wait之后进行notify唤醒,代码里面sleep了3秒可以达到这个目的,这个想了好久。 我的理解是:主线程的locker.notifyAll()会跟其他5个线程locker.wait()同时抢锁 Object locker,由于主线程 sleepSec(workingSec + 1); 大于其他5个线程的 sleepSec(workingSec);,同时由于locker锁对象是共享的,所以会导致锁最开始被其他5个线程抢到,直到5个线程都进入wait后,被释放的锁才被主线程抢到,最终保证了notifyAll在所有进入wait之后进行。这样理解正确吗?

作者回复: 从程序设计和主管感觉上,是的。但是这相当于是创造了一种比较理想的条件,让主线程排在worker的后面。 为什么呢?后面也会讲到,synchronized不是公平锁,也就是说,是不保证先来先得的。重点词:不保证。不保证的意思是说,不在保证锁的公平性上,花费太多的代价。 但是就我们这个例子来说,主线程因为多sleep了一秒钟,其实并不是一个非常严格的竞争环境,所以除非非常小的几率,否则主线程都还会排在worker线程后面拿到锁。

2020-05-04
3
蓦然回首
老师,花了点时间把javadoc上面关于wait和notify的相关文档看了一下,算是彻底搞明白它们的执行流程,大概整理了一下,老师看看我总结的是否正确: 1、wait使用的前提: The current thread must own this object's monitor lock 调用wait的线程必须拥有指定object的monitor(锁) (在视频中代码,即为在synchronized (locker)代码块中) 2、wait的作用: 调用wait的线程被加入指定object的wait集合中,并且放弃在指定object上的同步声明 (即失去指定object的monitor(锁),即会解锁,) This method causes the current thread (referred to here as <var>T</var>) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. (但仅仅是会失去指定object的monitor(锁),对于其他可能存在的锁没影响) Note that only the locks on this object are relinquished; any other objects on which the current thread may be synchronized remain locked while the thread waits. wait期间,线程调度不可用,线程处于休眠状态 Thread T then becomes disabled for thread scheduling purposes 3、线程调用wait后,再次被唤醒的条件: 1)其他线程使用指定object调用notify,并且该线程碰巧被作为随机选择的线程去被唤醒 2)其他线程使用指定object调用notifyAll 3)其他线程interrupts该线程 4)指定的超时时间过去了(wait指定了超时时间,如locker.wait(1000)) 当超时时间为0时,超时时间被忽略,线程会一直休眠直到因为上面三种因素被唤醒 5)线程被虚假唤醒(线程可以在没有通知、中断或超时的情况下被唤醒,这就是所谓的假唤醒) 4、线程被唤醒后的执行流程: 然后当线程被唤醒时,将从指定object对象的等待集合中删除,并重新开启线程调度。 此时,它将再次与其他线程竞争指定object对象的monitor(锁) , 一旦它重新获得了对对象的指定object对象的monitor(锁) ,线程将恢复到调用wait方法时的状态, 然后线程从wait方法的调用中返回 5、虚假唤醒的解决方案: 因为存在假唤醒,虽然这种情况在实践中很少发生,但应用程序必须通过测试导致线程被唤醒的条件来防止这种情况发生,如果条件不满足,则继续等待。 如下示例: synchronized (locker) { while (<condition does not hold> and <timeout not exceeded>) { long timeoutMillis = ... ; // recompute timeout values int nanos = ... ; obj.wait(timeoutMillis, nanos); } ... // Perform action appropriate to condition or timeout }

作者回复: 很好很全面

2020-11-18
2
jjn0703
请教一下老师,有个需求是去先走第三方接口拉取数据回来实时计算,但是有时候数据会过多,要求是最多拉十秒的数据,请问这个该怎么实现呀

作者回复: 这个接口如果是http的话,需要自己做一些控制。http本身并没有拉取数据过多导致超时这么个设置,只能说是连不上之类的网络问题导致timeout。 从我理解你的需求来看,即使拉取的数据超过10秒,其实也是在正常传输数据的,http自己不会因为这个断掉。 我的理解是,你这边的程序有个DataPuller,按照主程序的需求,向第三方发起http请求,拉取数据,然后主程序等待DataPuller返回数据,但是最多只想等待10秒,10秒内能结束最好,不能结束就有多少数据就给主程序多少就行了。 下面的示例代码供你参考: https://gist.github.com/deepnighttwo/b760bf0bbaddaaf3d3fb6e331684b54d /** * @author 臧萌 */ public class PullDataWithTimeLimitAppMain { public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(1); PullDataTask task = new PullDataTask(countDownLatch); new Thread(task).start(); System.out.println("Waiting for data pulling"); countDownLatch.await(10, TimeUnit.SECONDS); System.out.println("Finished waiting. Actually waited for " + (System.currentTimeMillis() - start) / 1000 + " sec. Data got from data puller is " + task.getDataList()); } } class PullDataTask implements Runnable { private List<Integer> dataList = new ArrayList<>(); private CountDownLatch countDownLatch; public PullDataTask(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { int timeSec = (int) (Math.random() * 20); System.out.println("going to pulling data for " + timeSec + " seconds"); IntStream.range(0, timeSec).forEach(i -> { try { Thread.sleep(TimeUnit.SECONDS.toMillis(1)); } catch (InterruptedException e) { e.printStackTrace(); } dataList.add(i); }); countDownLatch.countDown(); System.out.println("pulling finished"); } public List<Integer> getDataList() { return dataList; } }

2020-03-21
2
我的名字叫楠哥
学到线程的时候。就开始完全懵了😂

作者回复: 我学的时候可能比你还懵。多写,用心去感受:线程是执行代码的。代码是告诉线程执行的命令的

2022-04-12
1
笨笨
老师唤醒线程的优先级是最低的吗?为什么它是最后一个抢到锁的?

作者回复: 这个和线程的优先级是没关系的。这个例子里,notify线程是最后一个去抢锁的。我们可以假想所有抢锁的线程都在一个队列里等着,那么最后一个抢的,大概率会最后一个抢到。当然Java语言和Java规范并没有这样规定。

2021-01-16
1
笨笨
new Thread(() -> { System.out.println(getName() + ":线程开始工作....."); try { synchronized (locker) { sleepSec(workingSec); System.out.println(getName() + ":进入等待"); locker.wait(); System.out.println(getName() + ":线程继续...."); sleepSec(2); System.out.println(getName() + ":结束"); } } catch (InterruptedException e) { e.printStackTrace(); } }, "工作线程" + i).start();老师这段代码中的( () -> {} )进行了什么操作呢?

作者回复: () -> {} 就是lambda的表达式,也就是Thread执行的实际代码。 这个代码里面就是用sleep模拟工作,用locker去模拟获取不可共享的资源。

2020-11-29
1
蓦然回首
不过,文档中好像没有明确说明, notify和notifyAll本身是否会类似于wait一样,执行完之后就立马解锁了,然后再和其他线程一起来重新抢占,不然单独notify就没办法理解了,因为一直如果没解锁,locker本身一直被notify(notifyAll)的线程占有着,其他wait的线程根本没机会获取到锁,这个地方希望说明一下

作者回复: notify不会释放锁,notify本身是在synchronized里的,synchronized块结束就会释放锁。

2020-11-18
2
1
蓦然回首
另外线程能拥有指定object对象的monitor(锁),有三种方式(即synchronized作用在类的三种方式): 1)作用在类成员方法,即指定object对象的monitor(对象锁) 2)作用于类的静态成员方法上,指定类的类型锁(类锁) 3)作用于类的静态成员方法上,指定类的类型锁(类锁)

作者回复: 本质是一样的,对象上的锁。

2020-11-18
1
收起评论