wait放弃对象锁_终于搞懂了sleep/wait/notify/notifyAll,真的是不容易

0be2a9f07afa0715bdab1dcd25b6972a.png

sleep/wait/notify/notifyAll分别有什么作用?它们的区别是什么?wait时为什么要放在循环里而不能直接用if?

简介

首先对几个相关的方法做个简单解释,Object中有几个用于线程同步的方法:wait、notify、notifyAll。

public class Object { public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll();}
  • wait: 释放当前锁,阻塞直到被notify或notifyAll唤醒,或者超时,或者线程被中断(InterruptedException)
  • notify: 任意选择一个(无法控制选哪个)正在这个对象上等待的线程把它唤醒,其它线程依然在等待被唤醒
  • notifyAll: 唤醒所有线程,让它们去竞争,不过也只有一个能抢到锁
  • sleep: 不是Object中的方法,而是Thread类的静态方法,让当前线程持有锁阻塞指定时间

sleep和wait

sleep和wait都可以让线程阻塞,也都可以指定超时时间,甚至还都会抛出中断异常InterruptedException。

而它们最大的区别就在于,sleep时线程依然持有锁,别人无法进当前同步方法;wait时放弃了持有的锁,其它线程有机会进入该同步方法。多次提到同步方法,因为wait必须在synchronized同步代码块中,否则会抛出异常IllegalMonitorStateException,notify也是如此,可以说wait和notify是就是为了在同步代码中做线程调度而生的。

下面一个简单的例子展现sleep和wait的区别:

import java.util.Date;import java.util.concurrent.atomic.AtomicInteger;public class Main { // 日志行号记录 private AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Main main = new Main(); // 开启两个线程去执行test方法 new Thread(main::test).start(); new Thread(main::test).start(); } private synchronized void test() { try { log("进入了同步方法,并开始睡觉,1s"); // sleep不会释放锁,因此其他线程不能进入这个方法 Thread.sleep(1000); log("睡好了,但没事做,有事叫我,等待2s"); //阻塞在此,并且释放锁,其它线程可以进入这个方法 //当其它线程调用此对象的notify或者notifyAll时才有机会停止阻塞 //就算没有人notify,如果超时了也会停止阻塞 wait(2000); log("我要走了,但我要再睡一觉,10s"); //这里睡的时间很长,因为没有释放锁,其它线程就算wait超时了也无法继续执行 Thread.sleep(10000); log("走了"); notify(); } catch (InterruptedException e) { e.printStackTrace(); } } // 打印日志 private void log(String s) { System.out.println(count.incrementAndGet() + " " + new Date().toString().split(" ")[3] + "" + Thread.currentThread().getName() + " " + s); }}/* 输出:1 00:13:23Thread-0 进入了同步方法,并开始睡觉,1s2 00:13:24Thread-0 睡好了,但没事做,有事叫我,等待2s3 00:13:24Thread-1 进入了同步方法,并开始睡觉,1s4 00:13:25Thread-1 睡好了,但没事做,有事叫我,等待2s5 00:13:26Thread-0 我要走了,但我要再睡一觉,10s6 00:13:36Thread-0 走了7 00:13:36Thread-1 我要走了,但我要再睡一觉,10s8 00:13:46Thread-1 走了*/

对输出做个简单解释(已经看懂代码的童鞋可以跳过):

1 00:13:23Thread-0 进入了同步方法,并开始睡觉,1s // Thread-0首先进入同步方法,Thread-1只能门外候着2 00:13:24Thread-0 睡好了,但没事做,有事叫我,等待2s // Thread-0 sleep 1秒这段时间,Thread-1没进来,证明sleep没有释放锁3 00:13:24Thread-1 进入了同步方法,并开始睡觉,1s // Thread-0开始wait后Thread-1马上就进来了,证明wait释放了锁4 00:13:25Thread-1 睡好了,但没事做,有事叫我,等待2s // Thread-1也打算wait 2秒(2秒后真的能醒来吗?)5 00:13:26Thread-0 我要走了,但我要再睡一觉,10s // Thread-0已经wait超时醒来了,这次准备sleep 10s6 00:13:36Thread-0 走了 // 10s过去了Thread-0都sleep结束了,那个说要wait 2s的Thread-1还没动静,证明超时也没用,还得抢到锁7 00:13:36Thread-1 我要走了,但我要再睡一觉,10s // Thread-0退出同步代码后,Thread-1才终于得到了锁,能行动了8 00:13:46Thread-1 走了

notify和notifyAll

同样是唤醒等待的线程,同样最多只有一个线程能获得锁,同样不能控制哪个线程获得锁。

区别在于:

  • notify:唤醒一个线程,其他线程依然处于wait的等待唤醒状态,如果被唤醒的线程结束时没调用notify,其他线程就永远没人去唤醒,只能等待超时,或者被中断
  • notifyAll:所有线程退出wait的状态,开始竞争锁,但只有一个线程能抢到,这个线程执行完后,其他线程又会有一个幸运儿脱颖而出得到锁

如果觉得解释的不够明白,代码来一波:

import java.util.Date;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.atomic.AtomicInteger;public class Main { private AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Main main = new Main(); // 开启两个线程去执行test方法 for (int i = 0; i < 10; i++) { new Thread(main::testWait).start(); } Thread.sleep(1000); for (int i = 0; i < 5; i++) { main.testNotify(); } } private synchronized void testWait() { try { log("进入了同步方法,开始wait"); wait(); log("wait结束"); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void testNotify() { notify(); } private void log(String s) { System.out.println(count.incrementAndGet() + " " + new Date().toString().split(" ")[3] + "" + Thread.currentThread().getName() + " " + s); }}/* 输出:1 00:59:32Thread-0 进入了同步方法,开始wait2 00:59:32Thread-9 进入了同步方法,开始wait3 00:59:32Thread-8 进入了同步方法,开始wait4 00:59:32Thread-7 进入了同步方法,开始wait5 00:59:32Thread-6 进入了同步方法,开始wait6 00:59:32Thread-5 进入了同步方法,开始wait7 00:59:32Thread-4 进入了同步方法,开始wait8 00:59:32Thread-3 进入了同步方法,开始wait9 00:59:32Thread-2 进入了同步方法,开始wait10 00:59:32Thread-1 进入了同步方法,开始wait11 00:59:33Thread-0 wait结束12 00:59:33Thread-6 wait结束13 00:59:33Thread-7 wait结束14 00:59:33Thread-8 wait结束15 00:59:33Thread-9 wait结束*/

例子中有10个线程在wait,但notify了5次,然后其它线程一直阻塞,这也就说明使用notify时如果不能准确控制和wait的线程数对应,可能会导致某些线程永远阻塞。

使用notifyAll唤醒所有等待的线程:

import java.util.Date;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.atomic.AtomicInteger;public class Main { private AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Main main = new Main(); // 开启两个线程去执行test方法 for (int i = 0; i < 5; i++) { new Thread(main::testWait).start(); } Thread.sleep(1000); main.testNotifyAll(); } private synchronized void testWait() { try { log("进入了同步方法,开始wait"); wait(); log("wait结束"); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void testNotifyAll() { notifyAll(); } private void log(String s) { System.out.println(count.incrementAndGet() + " " + new Date().toString().split(" ")[3] + "" + Thread.currentThread().getName() + " " + s); }}/* 输出:1 01:03:24Thread-0 进入了同步方法,开始wait2 01:03:24Thread-4 进入了同步方法,开始wait3 01:03:24Thread-3 进入了同步方法,开始wait4 01:03:24Thread-2 进入了同步方法,开始wait5 01:03:24Thread-1 进入了同步方法,开始wait6 01:03:25Thread-1 wait结束7 01:03:25Thread-2 wait结束8 01:03:25Thread-3 wait结束9 01:03:25Thread-4 wait结束10 01:03:25Thread-0 wait结束*/

只需要调用一次notifyAll,所有的等待线程都被唤醒,并且去竞争锁,然后依次(无序)获取锁完成了后续任务。

为什么wait要放到循环中使用

一些源码中出现wait时,往往都是伴随着一个循环语句出现的,比如:

private synchronized void f() throws InterruptedException { while (!isOk()) { wait(); } System.out.println("I'm ok");}

既然wait会被阻塞直到被唤醒,那么用if+wait不就可以了吗?其他线程发现条件达到时notify一下不就行了?

理想情况确实如此,但实际开发中我们往往不能保证这个线程被notify时条件已经满足了,因为很可能有某个无关(和这个条件的逻辑无关)的线程因为需要线程调度而调用了notify或者notifyAll。此时如果样例中位置等待的线程不巧被唤醒,它就会继续往下执行,但因为用的if,这次被唤醒就不会再判断条件是否满足,最终程序按照我们不期望的方式执行下去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/244206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pygame 使用Djkstra广度搜索寻找迷宫(相对)最短路径

基于之前写的迷宫生成器实现了Djkstra算法搜索路径。 https://blog.csdn.net/ChillingKangaroo/article/details/122800431 Djkstra基于广度优先算法&#xff0c;与简单搜索不同的是Djkstra在访问每一个节点的时候会计算到该节点的最短路径以及上一个节点&#xff0c;如果有新…

python打乱list_超实用!每 30 秒学会一个 Python 小技巧,GitHub 标星 5300!

公众号关注 “GitHubDaily”设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01;很多学习 Python 的朋友在项目实战中会遇到不少功能实现上的问题&#xff0c;有些问题并不是很难的问题&#xff0c;或者已经有了很好的方法来解决。当然&#xff0c;孰能生巧&#xff0c;当…

(开源)Flask+Vue+Bootstrap3 人力资源用Web数据库

闲着没事写了一个轻量级web数据库&#xff0c;在网页端访问并操作SQL&#xff0c;可以进行Excel(xlsx)数据导入\导出&#xff0c;包含用户注册\登录\授权功能&#xff0c;密码有hash&#xff0c;授权目前比较简陋&#xff1a;后端使用了一个列表储存授权用户 已授权用户&#…

access找不到输入表或者dual_在Access窗体中显示指定路径的图片

↑↑↑点击上方图片&#xff0c;了解详情在Access中&#xff0c;如果把图形对象以OLE格式的字段保存&#xff0c;那么在窗体中可以直接显示出图片来。但是这样做有以下不足&#xff1a;一、需要将图片逐一插入到表中&#xff0c;工作量太大。二、使数据库文件变得庞大。三、相同…

LeetCode 168. Excel列表名称详解

刷到了这一道简单难度题 https://leetcode-cn.com/problems/excel-sheet-column-title/https://leetcode-cn.com/problems/excel-sheet-column-title/ 粗看就是一道进制转换题不过容易掉坑里。 首先略讲一下进制转换&#xff0c; 以701为例&#xff0c;该数字可以转换为以下…

可视化管理_RFID技术实施智能仓储管理可视化

仓储物流管理在各个行业都非常重要&#xff0c;RFID技术助力仓储物流信息管理提高供应链管理的透明度和库存周转率&#xff0c;这样有效减少缺货损失&#xff0c;提高企业内的仓储物流效率。仓库管理过程中&#xff0c;存在区域划分笼统&#xff0c;不容易辨识&#xff0c;货物…

(包含重力矢量)Pygame粒子模拟

半成品&#xff0c;目前速度不能修改&#xff0c;另外某些状况下路径会比较奇怪&#xff0c;因为没有速度计算&#xff0c;包含了重力矢量&#xff0c;可以修改重力方向 import pygame as pg import math import time import random import mathclass Particle(): #Tile is for…

小米蓝牙左右互联_解决不同品牌智能家居的兼容问题,小米米家智能多模网关发布...

如今智能家居种类可谓异常丰富&#xff0c;许多智能家居确实能让日常生活变得更加便捷。但是&#xff0c;相信许多智能家居爱好者都有一个烦恼&#xff0c;不同的品牌智能家居几乎不能实现交互&#xff0c;比如现在正使用Zigbee协议的智能家居&#xff0c;但新购置的智能家居却…

Pygame列表(链表)简单实现贪吃蛇

主要算法&#xff1a; 创建一个二维矩阵映射到屏幕上的像素&#xff0c;逻辑在该矩阵中实现 移动通过4个矢量完成&#xff0c;矢量储存在列表中按照 上右下左 的顺序排列&#xff08;顺时针90度&#xff09;&#xff0c; 当前矢量以0-3的数字表达&#xff0c;这样进行加二除…

Pygame 整活五子棋

很早之前写了一个类似的五子棋&#xff0c;没有做到pygame里面&#xff0c;闲着没事给整过来了&#xff0c;主要就是加了一个鼠标映射坐标。 表情被锤会变脸。 设置的0积分不知道能不能下载 https://download.csdn.net/download/ChillingKangaroo/82109145 代码不多&#x…

读进程和写进程同步设计_浅谈unix进程进程间通信IPC原理

什么是进程进程间通信进程间通信即为不同进程之间通信&#xff0c;进程同步是进程间通信的一种unix进程间通信的分类有哪些System V进程间通信方式包含&#xff1a;System V消息队列System V信号量System V共享内存UNIX进程间通信方式包含&#xff1a;匿名管道命名管道信号POSI…

(Ipython)Matplotlib 中将二叉树可视化

&#xff08;注意之前代码有错误目前已更新&#xff09; 最近学习黑红二叉树&#xff0c;我想如果把二叉树可视化在操作的时候如果出错会比较容易发现。 在网上搜了一圈只有比较简单的ascii 的代码。 自己用Ipython写了一个&#xff0c;比较适合学生。 PS&#xff1a;算法没…

其中一个页签慢_VBA实战技巧15:创建索引页

学习Excel技术&#xff0c;关注微信公众号&#xff1a;excelperfect在工作簿中有许多工作表时&#xff0c;我们可以创建一个单独的工作表当作索引页&#xff0c;在其中创建到每个工作表的链接&#xff0c;就像目录一样&#xff0c;不仅方便查看工作簿中的工作表名称&#xff0c…

Python使用OpenCV 卷积核 实现康威生命游戏

"Mozart, Beethoven, and Chopin never died. They simply became music." 康威生命游戏规则十分简单&#xff0c;简化后如下&#xff1a; 一个“细胞”&#xff08;或者说单元&#xff09;分为生或死两种状态&#xff0c; 如果活相邻细胞有2或3个活细胞 该细胞活…

verilog赋多位值_verilog赋值

我现在要用且只能用八位的拨片开关对两个四位变量t1l,t1h赋值&#xff0c;且这两个变量t1l,t1h是要输出的&#xff0c;所以我编了一下程序&#xff0c;先通过拨片开关对输入变量d0,d1赋值&#xff0c;然后将d0,d1的值赋给t1l,t1...我现在要用且只能用八位的拨片开关对两个四位变…

Python/OpenCV 使用傅里叶变换与高斯平滑分析轮廓轨迹

该方法基本思想是通过分析高低频信息检测出轮廓碰伤、运动轨迹突变等信息&#xff0c;在工业上应用可能比较广泛&#xff0c; 对各种不规则形状都能分析&#xff0c;不过对高频信息多的复杂形状可能不好区分形状与噪音。 在这个例子中讲使用一个有鼓包的鸡蛋 import numpy as…

实现mvcc_一文读懂 etcd 的 mvcc 实现

提到事务必谈 ACID 特性, 基于悲观锁的实现会有读写冲突问题&#xff0c;性能很低&#xff0c;为了解决这个问题&#xff0c;主流数据库大多采用版本控制 mvcc[1] 技术&#xff0c;比如 oracle, mysql, postgresql 等等。读可以不加锁&#xff0c;只需要读历史版本即可 (写写还…

Pygame 粒子物理:Numba实现同时渲染十万+像素

图中同时渲染了十万个像素&#xff0c;没有明显掉帧 我对Pygame的印象一直是慢的扣脚的&#xff0c;直到前段时间看到了一段MandelBrot代码&#xff08;源地址弄丢了&#xff09;其中使用了这个功能:pygame.surfarray.make_surface() 这里可以直接把numpy阵列转换为pygame.su…

“vector”: 不是“std”的成员_libcxx 的 std::function 源码分析

链接&#xff1a;functional。其中 std::function 的主体内容在 2100 多行。先来看 function 的头部。template<class _Rp, class ..._ArgTypes> class _LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)>: public __function::__maybe_derive_from_unary_function…

python验证身份证号码大全_身份证号码处理技巧大全

身份证号码处理技巧大全&#xff0c;汇总了常用的身份证号码处理六大技巧&#xff1a;不需要复杂的公式&#xff0c;点点鼠标即可完成&#xff0c;简单快捷&#xff0c;下面将详细介绍六大功能的具体用法。(文章最后有工具和演示文件的下载地址&#xff0c;可以下载下来同步操作…