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

news/2024/4/29 20:20:09/文章来源:https://blog.csdn.net/weixin_46114074/article/details/130729041

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

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

  

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.luyixian.cn/news_show_302290.aspx

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

相关文章

Python带你实现批量自动点赞小程序

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 所用知识点: 动态数据抓包 requests发送请求 json数据解析 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 requests 请求模块 &#xff0c;第三方&#xff0c;需安装 win R 输入cmd 输入安装命令 pip inst…

初步认识性能测试和完成一次完整的性能测试

上一篇博文主要通过两个例子让测试新手了解一下测试思想&#xff0c;和在做测试之前应该了解人几点&#xff0c;那么我们在如何完成一次完整的性能测试呢&#xff1f; 测试报告是一次完整性能测试的体现&#xff0c;所以&#xff0c;这里我给出一个完整的性能测试报告&#xff…

springBoot中使用redis实现分布式锁实例demo

首先 RedisLockUtils工具类 package com.example.demo.utils;import org.junit.platform.commons.util.StringUtils; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.red…

SAP入门到放弃系列之需求管理的基本要素

需求管理目标&#xff1a; 一般而言&#xff0c;生产计划&#xff08;PP&#xff09;的总体目标&#xff0c;特别是需求管理的总体目标是通过减少以下内容来更好地为客户服务&#xff1a; 补货提前期存货成本 需求管理的要素&#xff1a; 需求管理工作的主要要素广义上可分…

❤ cannot read properties of null(reading appendChild)解决办法

❤ 操作元素报&#xff1a;cannot read properties of null(reading appendChild)解决办法 1、场景&#xff1a; 写的一个js渲染&#xff0c;但是出了个小问题&#xff0c;cannot read properties of null(reading appendChild)报错。 <div id"divps" class&qu…

机器学习项目实战-能源利用率 Part-1(数据清洗)

1. 项目背景 2009年的《当地法案84号》&#xff0c;或纽约市基准法案&#xff0c;要求对能源和用水量进行年度基准测试和披露信息。被覆盖的财产包括单个建筑物的税收地块&#xff0c;其总建筑面积大于50,000平方英尺&#xff08;平方英尺&#xff09;&#xff0c;以及具有超过…

OpenAI新作Shap-e算法使用教程

一、知识点 Shap-e是基于nerf的开源生成3d模型方案。它是由如今热火朝天的Open AI公司&#xff08;chatgpt&#xff0c;Dell-E2&#xff09;开发、开源的。Shap-e生成的速度非常快&#xff0c;输入关键词即可生成简单模型&#xff08;限于简单单体模型&#xff09;。 二、环境…

别去外包,干了三年,废了....

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

jetson nx 用windows远程连接

VNC Viewer远程连接 一、jetson nx配置vnc 1、安装客户端 sudo apt-get install xrdp vnc4server xbase-clients2、进入nano/nx桌面&#xff0c;打开“Setting–>Desktop sharing”&#xff0c;没反应&#xff0c;据说是bug&#xff0c;我试过nano和nx都一样。首先输入下…

springboot+jsp法律知识分享网站普法平台

法律知识分享平台&#xff0c;主要的模块包括查看主页、个人中心、用户管理、律师事务所管理、律师管理、法律资讯管理、案例分析管理、案例分享管理、法规信息管理、法规分享管理、留言信息管理、留言回复管理、论坛管理、系统管理等功能。系统中管理员主要是为了安全有效地存…

Docker笔记7 | 如何使用 Docker Compose 搭建一个拥有权限 认证、TLS 的私有仓库?

7 | 如何使用 Docker Compose 搭建一个拥有权限 认证、TLS 的私有仓库&#xff1f; 1 准备工作2 准备站点证书2.1 创建CA私钥2.2 创建CA根证书请求文件2.3 配置CA根证书2.4 签发根证书2.5 生成站点SSL私钥2.6 私钥生成证书请求文件2.7 配置证书2.8 签署站点SSL证书 3 配置私有仓…

低代码行业的发展真的可以让复杂的代码编写一去不复返?

前言 传统的软件开发过程往往需要耗费大量的时间和精力&#xff0c;因为开发人员需编写复杂的代码以完成各种功能。 低代码行业的发展&#xff0c;正好解决了这个问题&#xff0c;让复杂的代码编写一去不复返了。 文章目录 前言引入强大的平台总结 引入 低代码平台 是一种通过可…

Go基础篇:接口

目录 前言✨一、什么是接口&#xff1f;二、空接口 interface{}1、eface的定义2、需要注意的问题 三、非空接口1、iface的定义2、itab的定义3、itab缓存 前言✨ 前段时间忙着春招面试&#xff0c;现在也算告一段落&#xff0c;找到一家比较心仪的公司实习&#xff0c;开始慢慢回…

yum和repo详细解析

目录 一、rpm、yum、repo 二、repo文件详细解析 三、常用命令 四、更改epel.repo为清华源 一、rpm、yum、repo RPM RPM(Red-hat Package Manager)&#xff0c;是一个由红帽最早开发出来的包管理器&#xff0c;目前已经是大多数Linux发行的默认包管理器。RPM管理的包都是以…

程序员一个月拿两万,得知卖猪肉可以赚五万,你是选择做程序员还是卖猪肉?

在知乎上看到这么个帖子&#xff0c;觉得挺有意思&#xff0c;大家一起瞧瞧&#xff1f; 对此&#xff0c;我也看到了许多犀利的回答哈 **A&#xff1a;**我反过来问你&#xff0c;如果一对夫妇卖猪肉一个月只能挣一万&#xff0c;听说一名程序员一个月拿五万&#xff0c;他们…

刷题day66:目标和

题意描述&#xff1a; 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添…

【简介】限流

限流 为什么要限流限流算法单机限流计数器算法滑动窗口算法漏桶算法令牌桶算法 分布式限流配额算法 限流策略限流位置 为什么要限流 作为有追求的程序员&#xff0c;我们都希望自己的系统跑的飞快&#xff0c;但是速度再快&#xff0c;系统处理请求耗时也不可能为0&#xff0c…

[MYAQL / Mariadb] 数据库学习-管理表记录2:匹配条件

管理表记录-匹配条件 匹配条件基本条件查询逻辑匹配&#xff08;多个条件判断&#xff09; 高级条件范围匹配模糊查询正则表达式&#xff1a; regexp四则运算 操作查询结果&#xff08;对查找到的数据再做处理&#xff09;排序分组&#xff08;一样的显示一次&#xff09;&…

【网络】交换机基本原理与配置

目录 &#x1f341;交换机工作原理 &#x1f341;交换机接口的双工模式 &#x1f341;交换机命令行模式 &#x1f341;交换机常见命令 &#x1f9e7;帮助命令 &#x1f9e7;常用命令介绍 &#x1f341;交换机的基本配置 &#x1f9e7;配置接口的双工模式及速率 &#x1f990;博…

janus videoroom 对接freeswitch conference 篇1

janus videoroom 实时性非常好&#xff0c; freeswitch conference的功能也很多 &#xff0c;有没办法集成到一块呢 让很多sip 视频终端也能显示到videoroom 里面&#xff0c; 实现方式要不两种 1.改源码实现 &#xff08;本文忽略 难度高&#xff09; 2.找一个videoroom管…