JavaEE(系列6) -- 多线程(解决线程不安全系列1-- 加锁(synchronized)与volatile)

news2024/10/6 4:06:04

首先我们回顾一下上一章节引起线程不安全的原因

本质原因:线程在系统中的调度是无序的/随机的(抢占式执行)

  

1.抢占式执行

2.多个线程修改同一个变量.

        一个线程修改一个变量=>安全

        多个线程读取同一个变量=>安全

        多个线程修改不同的变量=>安全

3.修改操作,不是原子的.(最小不可分割的单位)

例如:对一个变量进行自增操作可分为3步:1.load 2.add 3.save

其中一个操作对应单个CPU指令,是原子的.如果跟上述自增这个操作,对应三步,也就对应多个CPU指令,大概率就不是原子的.

4.内存可见性(本章内容进行讲解)

5.指令重排序(本章内容进行讲解)

目录

1. 解决线程抢占式执行  -- 加锁

如何进行加锁呢?

Synchronized的用法(其他) 

2. 内存可见性

3. 指令重排序


1. 解决线程抢占式执行  -- 加锁

我们学过的join不能防止线程抢占执行吗?

这个思想是一个办法,不过如果这么搞,就不需要多线程了,直接一个线程串行执行。

多线程的初心:进行并发编程,更好地利用多核CPU.

那么如何保证自增这个操作,是一个原子的呢?--->加锁

举例:

生活中常见的例,去公共厕所.

 上厕所,打开门进去,把门锁了。上完厕所,解锁,打开门离开.

锁的核心操作有两个

1.加锁

2.解锁

         一旦某个线程加锁了之后,其他线程也想加锁,就不能直接加上了,就需要阻塞等待,一直等到拿到锁的线程释放锁了为止。

记得,线程调度,是抢占式执行的

当1号释放锁之后,等待的2和3和4,谁能抢先一步拿到锁,那是不确定的了。图中就是3号老铁抢到了.

此处的“抢占式执行”导致了线程之间的调度是“随机”的。

如何进行加锁呢?

synchronnized是java中的关键字,直接使用这个关键字来实现加锁效果.

具体如下图所示

package threading;
class Counter{
    private int count=0;
    public void add(){
        synchronized (this){//加锁
            count++;
        }
 
    }
    public int get(){
        return count;
    }
}
public class ThreadDemo10 {
    public static void main(String[] args) throws InterruptedException{
        Counter counter=new Counter();
        //搞两个线程,两个线程分别对这个counter自增5w次
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                counter.add();
            }
        });
        t1.start();
        Thread t2=new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.get());
    }
}

运行结果:

此时来说明一个点:

join是完全让两个线程变成串行的,而加锁是将两个线程的一小部分变成串行,而不加锁的部分还是并发执行的.

加锁之前:

在上述代码中,一个线程做的工作大概是这些:

1.创建i

2.判定i<50000

3.调用add

4.count++

5.add返回

6.i++

其中只有虽然都是并行的,但是因为自增操作不是原子性的,导致线程之间出现抢占式执行.

加锁之后:

其中只有count++是串行的,(抢到锁的先自增完,剩下的再自增)剩下的12356两个线程仍然是并发的。

在保证线程安全的前提下,同时还能让代码跑的更快一些,更好地利用下多核cpu。

无论如何,加锁都可能导致阻塞。代码阻塞,对于程序的效率肯定还是会有影响的。此处虽然是加了锁,比不加锁要慢些,肯定是比串行快,比不加锁算的准。

Synchronized的用法(其他) 

1.直接修饰普通成员方法  ==> 以this为锁对象进行加锁

2.修饰静态成员方法 ==> 以类对象为锁对象

 等价于下面==>

 

2. 内存可见性

首先给出一个结论:

所谓的内存可见性就是多线程环境下,编译器对于代码优化,产生了误判,从而引起了bug,进一步导致了我们代码的bug。

下面给出一个例子

package threading;
 
import java.util.Scanner;
 
public class ThreadDemo11 {
    public static int flag=0;
    public static void main(String[] args) {
 
        Thread t1=new Thread(()->{
            while (flag==0){
 
            }
            System.out.println("循环结束!t1结束!");
        });
        Thread t2=new Thread(()->{
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入一个整数");
            flag=scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

上述代码:我们创建了一个新的线程,里面写了一个循环,我们期待通过t2线程改变标志位,使得标志位变为非0,然后终止t1线程的循环.

运行代码,当我们多次输入1的时候,线程t1循环没有终止. 

出现的原因:

t1的这个循环,步骤是这样的,load从内存中读取数据到寄存器,cmp比较寄存器的值是否为0,

此时load的开销非常的大,要一直重复上述操作.读取内存虽然比读硬盘来的快,但是读寄存器,比读内存又要快。此时,编译器就做了一个非常大胆的操作,把load就给优化掉了。只有第一次执行load才真正的执行了后续循环都只cmp,不load(相当于是复用之前寄存器中的load过的值).这是编译器优化的手段,是一个非常普遍的事情,能智能地调整你的代码执行逻辑,保证程序结果不变地前提下,语句变化,通过一些列操作,让整个程序执行的效率大大提升。编译器对于“程序结果不变”单线程下判定是非常准确的。但是多线程不一定,可能导致调整后,效率提高,结果变了。

那么我们如何针对,这个操作进行修改呢?

1.可以让读寄存器的这个速度稍微慢下来,此时编译器就不会进行优化,也就会每次进行重新load这个操作,那么就会读取到真正的修改后标志位的值. 

public class ThreadDemo12 {
    public static int flag = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while (flag == 0){
                try {
                    Thread.sleep(10);
                    //加了sleep就让循环执行的很慢,编译器就不会进行优化.load
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("循环结束!t1结束");
        });
        Thread t2 = new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入一个整数:");
            flag = scanner.nextInt();
        });

        t1.start();
        t2.start();

    }
}

 运行结果:我们可以看到,我们成功的将线程t1的循环停止下来了.

 那么除了上述操作,我们还可以使用volatile关键字来进行修改.

被volatile修饰的变量,此时编译器就会禁止上述优化,能够保证每次都是从内存重新读取数据。

 注意:

3. 指令重排序

这就是volatile的另一个作用了,防止指令重排序

什么事指令重排序呢?

指令重排序:也是编译器优化的策略,调整了代码执行的顺序,让程序更高效。前提也是保证整体逻辑不变。谈到优化,都要保证调整之后的结果和之前是不变得。单线程下容易保证,多线程就不好说了。

举例:

就拿房子装修来说:

A买了一个新房子(精装修)

B买了一个新房子(毛坯房)

那么A这个过程就是

1.交钱 2.房地产装修 3.交付钥匙

那么B这个过程就是

1.交钱 3.交付钥匙 2.房地产装修

最后AB都拿到了一样的房子(假设装修队是一样的),但是这个过程是不一样的.

 上述伪代码

t1中的语句大体可以分为三个操作:

1.申请内存空间——交钱

2.调用构造方法(初始化内存的数据)——装修

3.把对象的引用赋值给s(内存地址的赋值)——拿到钥匙

如果是单线程环境,此处就可以指令重排序:

1肯定先执行,2和3谁先执行,谁后执行,都可以。

  

那么如果两个线程按照两种不同的方式执行呢?

如果t1按照 1 3 2的顺序执行,当t1执行完1 3 之后,即将执行2的时候,t2开始执行。由于t1的3已经执行过了,这个引用已经非空了。t2开始调用s.learn()。但是由于t1还没有初始化,learn的结果是什么不知道了,(也就是相当于A拿到了毛坯房的钥匙,这不就坏了吗),就出现了bug。

这个代码难以演示,因为大部分情况是正确的。

上述情况,不好演示,因为大部分是正确的,但是也会发生,那么使用volatile关键字修饰这个对象进行修饰,就会保证不会受到指令重排序的影响.

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

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

相关文章

容器安装Datax+Datax-web2.1(一)

目录 简介1、安装Datax-web2.1.11&#xff09;安装docker-compose2&#xff09;创建Datax-web和MySQL容器 2、安装Datax-web2.1.21&#xff09;安装MySQL2&#xff09;初始化数据3&#xff09;安装datax和datax-web4&#xff09;浏览器登录 DataxDatax-web2.1实现MySQL数据库数…

LeetCode 21. 合并两个有序链表 | C++语言版

LeetCode 21. 合并两个有序链表 | C语言版 LeetCode 36&#xff1a; 两个链表的第一个公共结点题目描述解题思路思路一&#xff1a;使用前缀和代码实现运行结果参考文章&#xff1a; 思路二&#xff1a;减少遍历节点数代码实现运行结果参考文章&#xff1a; LeetCode 36&#x…

CANoe-如何在Trace窗口显示SYN和FIN报文、同一条以太网报文在Trace窗口中的多条显示

1、如何在Trace窗口显示SYN和FIN报文 当我们使用CANoe实现TCP通信时,希望在Trace窗口直观显示报文的类型:SYN、ACK、FIN。显然Trace窗口也是支持这样的功能的。但很多时候由于一些人为的不正确的设置和配置,造成无法显示。 如果想解析出SYN报文,首先在Trace窗口选择正确的…

5个高清视频素材网站,免费下载~

免费高清视频素材网站&#xff0c;这几个你一定要知道&#xff0c;建议收藏&#xff01; 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYxMjky 菜鸟图库网素材非常丰富&#xff0c;网站主要还是以设计类素材为主&#xff0c;高清视频素材也很多&#xff0c;像风景、…

dcdc降压电路原理及仿真

在之前的文章 DCDC 降压芯片基本原理及选型主要参数介绍 中已经大致讲解了dcdc降压电路的工作原理&#xff0c;今天再结合仿真将buck电路工作过程讲一讲。 基本拓扑 上图为buck电路的基本拓扑结构&#xff0c;开关打到1&#xff0c;电感充电&#xff1b;开关打到0&#xff0c;…

pdf怎么转换成jpg格式的图片,5种方法详细教程

pdf怎么转换成jpg格式的图片&#xff0c;为什么这样做呢&#xff1f;那是因为将PDF转换成JPG格式的主要原因是方便在演示文稿、网页或社交媒体等平台上展示和分享PDF文件的内容。JPG格式具有广泛的兼容性和易于传输的特点&#xff0c;而且可以轻松地进行编辑和调整大小。此外&a…

Java反射简单介绍_01

文章目录 1. 什么是反射2. Java中类加载的三个阶段3. 反射机制提供的相关类4. Java中获取Class类的三种方式5. Class类提供的功能5.1. 获取Field类方法5.2. 获取Method类方法5.3. 获取Constructor类方法5.4. Class中其他方法 1. 什么是反射 Java中的反射主要是体现在运行期间,…

uniapp 用css画五边形(app 小程序)

效果图 css .scoreLabel{ background: $yxs-theme-color; width: 64rpx; height: 69rpx; line-height: 32rpx; font-size: 28rpx; font-family: DINPro; f…

点亮未来明灯,引领绿色革命

随着全球气候变化日趋严重&#xff0c;能源转型成为解决气候问题和提高全球能源安全合理性的必要措施之一。可持续能源技术因其对环境的友好性和可再生性而成为了当前热点话题。可持续能源技术已经成为人们日益关注的焦点。这项技术可以帮助我们减少对化石燃料的依赖&#xff0…

如何选择适合自己的小程序开发框架

随着微信、支付宝等开放平台的壮大&#xff0c;移动应用生态市场的蓬勃发展&#xff0c;例如小程序已经成为各个企业和开发者的重要选择。为了提高小程序的开发效率和代码重用性&#xff0c;许多第三方开发框架应运而生。 准备为需要的朋友整理一些常见的小程序第三方开发框架…

能伸展脖子的机器人?东京大学最新研究成果:基于鸵鸟肌肉骨骼结构和行为,具有高度灵活性的新型机械臂—RobOstrich(附论文)

原创 | 文 BFT机器人 得益于高度灵活的颈部&#xff0c;鸟类可以做很多事情&#xff0c;无论是转过头梳理自己的后背&#xff0c;在飞行过程中“眼观六路”&#xff0c;还是在地面或树上难以触及的角落和缝隙寻找食物。而在所有鸟类中&#xff0c;鸵鸟以其结实灵巧的颈部脱颖而…

顶部菜单栏-popuwindows

效果 布局文件 猜想与步骤 1.通过.9.png 制作尖尖效果&#xff0c; 2.popuwindows弹出布局框 以及灰色背景 3.点击按钮进入不同功能

QT运行程序后出现无法打开输出文件问题

:-1: error: cannot open output file release\Dailin.exe: Permission denied collect2.exe:-1: error: error: ld returned 1 exit status 如上所示报错。这个是因为用户写的程序开辟空间后没有释放造成的。用户需要把开辟了空间释放就可以了。 我的例子如下。 My_Uart *Ua…

【聚焦】“饶派杯”XCTF车联网安全挑战赛即将开启!

为深入贯彻落实国家网络强国和交通强国战略部署&#xff0c;推动智能网联汽车技术与产业发展、加快该领域人才培养、提升行业创新&#xff0c;打造自主可控、自主研发的中国智能网联汽车安全生态体系&#xff0c;由江西省委网信办、江西省工信厅、上饶市人民政府主办&#xff0…

【云渲染案例】《长月烬明》特效出圈?国内9家视效公司联手打造国风新仙侠!丨瑞云渲染案例

仙侠剧是中国电视剧的一大特色&#xff0c;也是很多观众喜爱的类型。近年来&#xff0c;仙侠剧的制作水平越来越高&#xff0c;由鞠觉亮执导&#xff0c;罗云熙、白鹿领衔主演的《长月烬明》就是一部在今年四月份引起了轰动的虐恋仙侠剧&#xff0c;它以炫丽的特效、新颖的剧情…

目前流行的9大前端框架

1. React 2. Vue 3. Angular 、 4. Svelte 官网&#xff1a;https://svelte.dev 中文官网&#xff1a;https://www.sveltejs.cn Svelte 是一种全新的构建用户界面的方法。传统框架如 React 和 Vue 在浏览器中需要做大量的工作&#xff0c;而 Svelte 将这些工作放到构建应用程…

WLAN,AP等

无线网络部署方案&#xff1a; AC直连式组网 AC旁挂式组网 WLAN 是无线局域网&#xff08;Vireless Local Area Network&#xff09;的简称 也称之为 Wi-Fi 简单理解&#xff1a;无线就是网线的一个延长 AC&#xff1a;无线控制器--统一管理AP AP&#xff1a;散发无线…

nginx 服务器总结

一. 负载均衡的作用有哪些&#xff1f; 1、转发功能 按照一定的算法【权重、轮询】&#xff0c;将客户端请求转发到不同应用服务器上&#xff0c;减轻单个服务器压力&#xff0c;提高 系统并发量。 2、故障移除 通过心跳检测的方式&#xff0c;判断应用服务器当前是否可以正常…

原神个人服务器替换模型(3Dmigoto)教程

原神个人服务器替换模型(3Dmigoto)教程 本期教程教大家如何修改原神私服角色模型 首先下载3Dmigoto &#xff08;3Dmigoto是开源的自己有兴趣想法的在GIT搜&#xff09; 打开3Dmigoto文件内的d3dx.ini文件 找到target &#xff08;我们需要找到有路径的这个选项&#xff09; …

【Vue基础】Element案例学习-智能学习辅助系统

一、效果展示 初步设计一个系统&#xff0c;有目录、搜索栏、表格操作等。 二、参考代码 主要关注上图“App.vue”和“BtestView.vue”两个文件的代码 1、App.vue <template><div ><!-- <h1>{{ message }}</h1> --><!-- <element-view&…