线程的同步(synchronized的原理和用法,解决线程同步时的通信问题)

线程的同步锁(synchronized)

为什么会出现线程的同步锁?

因为JVM虚拟机是抢占调度模型,当多个线程在同时访问一个资源时会发生两个线程争抢一个资源,在一个线程没有执行结束时,另一个线程抢到资源,造成线程不安全的问题

这里使用一道简单的练习题说明:

package Threadday2;import java.util.concurrent.atomic.AtomicInteger;public class Test01 {public static void main(String[] args) throws InterruptedException {// 创建线程AddThread addThread = new AddThread(); //自增线程DecThread decThread = new DecThread(); //自减线程// 启动线程addThread.start();decThread.start();// 线程插队addThread.join();decThread.join();System.out.println(Counter.count);}
}class Counter {//方式一:使用锁public static int count = 0;
}class AddThread extends Thread {//方式一:public void run() {for (int i = 0; i < 10000; i++) {Counter.count += 1;}}
}class DecThread extends Thread {//方式一:public void run() {for (int i = 0; i < 10000; i++) {Counter.count -= 1;}}
}

在这里插入图片描述

创建两个线程,一个线程计算将变量count从1加到10000,一个线程将count–,再这种状态下两个线程执行结果的count变量不为0,所有会有线程不安全的状况,要想解决这个问题就要使用synchronized(线程同步锁)

下面是使用线程同步锁解决问题的逻辑及代码

  • 首先,在Counter类中创建一个静态的Object类的常量当做锁对象
  • 其次,对需要加锁的代码进行加锁
package Threadday2;import java.util.concurrent.atomic.AtomicInteger;public class Test01 {public static void main(String[] args) throws InterruptedException {// 创建线程AddThread addThread = new AddThread(); //自增线程DecThread decThread = new DecThread(); //自减线程// 启动线程addThread.start();decThread.start();// 线程插队addThread.join();decThread.join();System.out.println(Counter.count);}
}class Counter {//方式一:使用锁public static int count = 0;public static final Object LOCK = new Object();}class AddThread extends Thread {//方式一:public void run() {for (int i = 0; i < 10000; i++) {// 加锁synchronized (Counter.LOCK) {Counter.count += 1;}}}
}class DecThread extends Thread {//方式一:public void run() {for (int i = 0; i < 10000; i++) {// 加锁synchronized (Counter.LOCK) {Counter.count -= 1;}}}
}

在这里插入图片描述

这个就是一种synchronized线程同步锁的应用(是使用代码块的方式:通过一个公共对象,作为“锁”)

synchronized用法

1.通过代码块的方式创建:通过一个公共对象,作为“锁”(上面的案例就是运用了这种方法)

2.通过实例方法:方法声明中直接使用synchronized关键字(作用等同于使用this作为锁)

package Threadday2;public class Test04 {//方法一:使用当前this对象,作为锁对象public void dosth1() {synchronized (this) {System.out.println("............");}}// 实例方法//方法二:方法声明中直接使用synchronized关键字(作用等同于使用this作为锁)public synchronized void dosth2() {}
}

3.静态方法:方法声明中直接使用synchronized关键字(作用等同于使用当前类的class对象作为锁)

package Threadday2;public class Test05 {//方法一:使用当前类的class对象,作为锁对象public static void dosth1() {synchronized (Test05.class) {System.out.println("............");}}//静态方法//方法二:方法声明中直接使用synchronized关键字(作用等同于使用当前类的class对象作为锁)public synchronized static void dosth2() {}
}
synchronized原理

原理:通过monitorenter/monitorexit指令实现监视器机制。

Moniter监视器

每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取monitor。当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:

  1. 若monitor的进入数为0,线程进入monitor,并将monitor的进入数量为1,当前线程成为monitor的owner(所有者)
  2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的数量加1
  3. 若其他线程已经占有monitor的所有权,那么当前尝试monitor所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权

对monitorexit的理解:

  1. 能执行monitorexit的线程一定是拥有当前对象的monitor的所有权的线程
  2. 执行monitorexit时,会将monitor的进入数减一,当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他线程就可以尝试获取monitor的所有权
获取步骤
  • 执行monitorenter指令,判断当前monitor监视器的线程进入数量:如果为0,则该线程直接进入监视器owner,代表该线程拥有监视器,同时进入数设置为1;
  • 如果线程已经拥有该monitor监视器,重新进入,则进入数+1;如果其它线程尝试获取监视器,则进入阻塞区,线程进入阻塞状态,直到监视器的进入数为0;
  • 执行monitorexit指令,进入数-1
锁升级(锁膨胀)
  • 偏向(斜)锁:只有一个线程执行的场景,使用偏向锁;
  • 轻量级锁:发现多个线程执行的场景,偏向锁升级为轻量级锁;轻量级锁不能处理并发;
  • 重量级锁:发现多个线程并发执行的场景,轻量级锁升级为重量级锁;重量级锁通过操作系统的“互斥锁”实现。互斥锁实现线程之间的切换,需要从“用户态”切换到“内核态”,付出高昂的代价,会导致性能下降。

线程同步时的通信问题

这里我使用一个案例来说明线程同步时的通信问题,当我创建两个线程交替打印,一个线程是数字线程,一个线程是字母线程,交替打印出12A 34B 56C 78D 910E …等时;

我的解题思路是先判断当数字等于奇数是打印出空格,当数字等于偶数是将数字线程处于等待状态,在字母线程打印出字母后,将字母线程处于等待状态,但是这么写就会导致在打印出 12A 后线程不执行了,线程不执行的原因是,因为两个线程都处于等待状态,所有要在他们进入等待状态前唤醒所有处于等待状态的线程

下面是代码分享

Number类(代表的是数字线程

package Threadday2;public class Number implements Runnable {private final Object LOCK;public Number(Object lock) {LOCK = lock;}@Overridepublic void run() {synchronized (LOCK) {for (int i = 1; i <= 52; i++) {if (i % 2 == 1) {System.out.print(" ");}System.out.print(i);if (i % 2 == 0) {// 唤醒所有处于等待状态的线程(字母线程)LOCK.notifyAll();try {// 数字线程进入等待状态LOCK.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}

Charater类(代表字母线程

package Threadday2;public class Charater implements Runnable {private final Object LOCK;public Charater(Object lock) {LOCK = lock;}@Overridepublic void run() {synchronized (LOCK) {for (char c = 'A'; c <= 'Z'; c++) {System.out.print(c);//唤醒所有处于等待状态的线程(数字线程)LOCK.notifyAll();if (c < 'Z') {try {//字母线程进入等待状态LOCK.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}

执行线程的代码

package Threadday2;public class Test03 {public static void main(String[] args) {final Object lock = new Object();Thread thread1 = new Thread(new Number(lock),"数字子线程");Thread thread2 = new Thread(new Charater(lock),"字母子线程");thread1.start();thread2.start();}
}

通过上面的案例我们会得知:
线程同步时的通信:

  • wait():必须通过 notify() / notifyAll()方法唤醒等待的线程
  • wait(等待时间):达到计时时间后,自动唤醒

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

动态规划--持续更新篇

将数字变成0的操作次数 1.题目 2.思路 在numberOfSteps函数中&#xff0c;首先设置f[0]为0&#xff0c;因为0已经是0了&#xff0c;不需要任何步骤。然后&#xff0c;使用一个for循环从1迭代到输入的整数num。对于每个整数i&#xff0c;如果i是奇数&#xff0c;则将f[i]设置为…

igolang学习3,golang 项目中配置gin的web框架

1.go 初始化 mod文件 go mod init gin-ranking 2.gin的crm框架 go get -u github.com/gin-gonic/gin 3.go.mod爆红解决

uniapp开发微信小程序跳转到另一个小程序中

注意&#xff1a;一开始我的云上务工模块是单独的tabbar界面&#xff0c;但是小程序跳转好像不能直接点击tabbar进行&#xff0c;所以我将这里改成了点击首页中的按钮进行跳转 点击这里进行小程序跳转 目录 基础讲解 uniapp小程序跳转的两个方法 调用说明&#xff08;半屏跳转…

文件上传漏洞 (upload部分通关教程)

一、抓包修改后缀名绕过前端验证&#xff0c;实现上传木马&#xff08;第一关&#xff09; 只适合只有前端验证的网站 一句话木马&#xff1a; <?php eval($_POST[a]); ?> 保存到文件中&#xff1a; 使用第一关直接上传&#xff0c;报错&#xff1a; 按照网站要求…

国内Twitter账号注册要注意什么?注册多个Twitter账号如何防止被封?

在这个数字化快速发展的时代&#xff0c;Twitter作为一个全球性的社交媒体平台&#xff0c;对于个人品牌塑造乃至跨境电商均有着不可忽视的影响力。然而&#xff0c;在中国大陆地区&#xff0c;对于Twitter账号注册以及如何安全地注册多个Twitter账号这样的话题&#xff0c;往往…

《初阶数据结构》尾声

目录 前言&#xff1a; 《快速排序&#xff08;非递归&#xff09;》: 《归并排序》&#xff1a; 《归并排序&#xff08;非递归&#xff09;》&#xff1a; 《计数排序》&#xff1a; 对于快速排序的优化&#xff1a; 分析&#xff1a; 总结&#xff1a; 前言&#xff1a…

vue3 toRefs之后的变量修改方法

上效果 修改值需要带上解构之前的对象名obj&#xff0c; changeName:()>{ // toRefs 解决后变量修改值方法&#xff1a; 解构前变量.字段新值 obj.name FEIFEI; } } 案例源码 <!DOCTYPE html> <html> <head><me…

APP被针对攻击了,要怎么解决

随着APP行业的兴起&#xff0c;游戏公司异军突起&#xff0c;不管是在控证还是攻击方面都是属于最复杂的一个场面&#xff0c;游戏APP逐渐成为DDOS流量攻击的“重灾区”。没有提前做好了解就盲目进军游戏APP行业&#xff0c;一旦被攻击就会让公司束手无策。那么&#xff0c;刚上…

【前端素材】推荐优质后台管理系统APP Zina平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时&#xff0c;可以将其功能和定义进一步细分&#xff0c;以便更好地理解其在不同方面的作用和实际运作。 1. 功能层次 a. 用户管理功能&#xff1a; 用户注册和登录&#xff1a;管理用户账户的注册和登录过程。权限管…

Vue 中 onclick和@click区别

文章目录 一、直接上结论二、验证代码&#xff0c;可直接运行三、点击结果 一、直接上结论 onclick 只能触发 js的原生方法&#xff0c;不能触发vue的封装方法click 只能触发vue的封装方法&#xff0c;不能触发js的原生方法 二、验证代码&#xff0c;可直接运行 <!DOCTYP…

【LeetCode刷题笔记】242.有效的字母异位词

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…

【寸铁的刷题笔记】树、dfs、bfs、回溯、递归(一)

【寸铁的刷题笔记】树、dfs、bfs、回溯、递归(一) 大家好 我是寸铁&#x1f44a; 总结了一篇刷题关于树、dfs、bfs、回溯、递归的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 105. 从前序与中序遍历序列构造二叉树 模拟分析图 代码实现 /*** Definition for a binary tre…

DataDreamer:让创建自定义数据集轻松无比!还自带标注!

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是Ash…

基于EasyCVR视频汇聚系统的公安网视频联网共享视频云平台建设思路分析(二)

一、需求分析 随着科技的飞速发展&#xff0c;视频监控联网技术在公安工作中发挥着越来越重要的作用。为了提高公安部门对各类事件的响应速度和处理能力&#xff0c;建设一个高效、稳定的公安视频联网共享云平台显得尤为重要。通过建设开放的视频联网共享云平台&#xff0c;实…

非洲数字经济持续崛起 本地化策略让传音提前入局

非洲市场&#xff0c;被誉为全球最后的“边疆级”市场&#xff0c;吸引着全球目光。近日&#xff0c;非洲开发银行最新报告指出&#xff0c;未来两年非洲的经济增长将优于世界其他地区&#xff0c;2023 年和 2024 年实际国内生产总值 (GDP) 平均约为 4%。广阔的非洲大陆焕发着勃…

【spring】 ApplicationListener的使用及原理简析

文章目录 使用示例&#xff1a;原理简析&#xff1a; 前言&#xff1a;ApplicationListener 是spring提供的一个监听器&#xff0c;它可以实现一个简单的发布-订阅功能&#xff0c;用有点外行但最简单通俗的话来解释&#xff1a;监听到主业务在执行到了某个节点之后&#xff0c…

【Python笔记-设计模式】对象池模式

一、说明 用于管理对象的生命周期&#xff0c;重用已经创建的对象&#xff0c;从而减少资源消耗和创建对象的开销 (一) 解决问题 主要解决频繁创建和销毁对象所带来的性能开销问题。如数据库连接、线程管理、网络连接等&#xff0c;对象的创建和销毁成本相对较高&#xff0c…

SOLIDWORKS Visualize 界面介绍

现在有越来越多的朋友在工作中选择使用SOLIDWORKS Visualize正版软件&#xff0c;这真是太棒了!这次的主题是小索带大家了解SOLIDWORKS Visualize界面&#xff0c;让更多的朋友快速的熟悉SOLIDWORKS Visualize界面。 【菜单栏】位于界面的顶端&#xff0c;菜单栏包含多个下拉菜…

[linux]进程间通信(IPC)———共享内存(shm)(什么是共享内存,共享内存的原理图,共享内存的接口,使用演示)

一、什么是共享内存 共享内存区是最快的&#xff08;进程间通信&#xff09;IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&#xff0c;换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。注意&#xff1a;…