首页 > 编程语言 >Java线程安全问题

Java线程安全问题

时间:2023-12-03 19:22:05浏览次数:42  
标签:count Java 变量 synchronized 安全 线程 内存

一、共享资源

共享资源是指,同时会有多个线程访问的资源。

二、线程安全问题

线程安全问题是指多个线程同时读写共享资源时并且没有任何同步措施的情况下,出现脏数据或者其他不可预见的结果的问题。当然如果所有线程都只是读取共享资源而不去修改共享资源是不会出现线程安全问题的。

三、Count计数器线程安全问题

在计数器类实现中,计数器变量count是一个共享变量也就是共享资源,当一个线程对count进行计数时,其步骤是:读取变量的值->对变量进行计数->将计数后的变量写回主内存三个步骤。

image

count初始值为0,线程A在t1时刻读取count的值,在t2时刻递增count的值,此时线程B从内存读取count的值(读取到的值为0),在t3时刻线程A将新的count值(1)写回主内存,线程B在t3时刻递增count的值,在t4时刻线程B将新count值(1)写回主内存。两个线程在对count值进行递增后,count最终值为1,明明是两次计数结果却不是2,这就是共享变量的线程安全问题。

四、Java内存可见性

4.1 Java内存模型

多线程处理下共享变量时的Java内存模型如下所示。

java内存模型

Java的内存模型规定将所有变量存放在主内存,当线程使用变量时,把主内存的变量复制到自己的工作内存里。线程读写操作的都是自己工作内存中的变量。

Java内存模型是一个抽象的概念,并不真实存在,下面是Java内存模型对应的CPU内存架构图:

cpu内存架构

上图为一个两个核心的CPU架构图,每个核都有自己的控制器和运算器以及一个独享的一级缓存。两个核共享的一个二级缓存和主内存。Java内存模型中的私有内存对应的就是一级缓存,主内存对应的是二级缓存或者是主内存。

4.2 共享变量内存不可见问题

参照Java内存模型和CPU架构图,我们模拟一下Count计数器示例中存在的Java共享变量内存不可见问题。

首先,线程A的操作如下:

线程A读取count(count=0)的值,先在一级缓存中查找count的值,此时一级缓存没有count的值,继续到二级缓存里找count的值,也没找到,到主内存里找count的值,将count的值复制一份副本到二级缓存和一级缓存(count=0);线程A对一级缓存里count执行计数操作(count=1);线程A将count的值写回到二级缓存和主内存。

此时,线程B执行如下操作:

线程B读取count的值,先在缓存中查找count的值,此时一级缓存没有count的值,继续到二级缓存中查找,找到count的值为1,将count值复制到线程B的一级缓存;线程B对一级缓存里的count值进行计数操作(count=2);线程B将count值写回二级缓存和主内存。

此时线程A再对count进行计数操作,这一次线程A在自己的一级缓存查找count的值,找到值为1;对count进行计数操作,此时count的值为2,然后将值写回二级缓存和主内存。我们知道在经过线程B的计数操作后,二级缓存和主内存中count的值为2,在线程A第二次进行计数操作后count的值应该是3,然而事实是经过线程A的第二次计数count的值依然是2。这就是内存不可见问题,线程B写入的值对线程A不可见。

4.3 synchronized关键字解决内存不可见问题

synchronized代码块是Java提供的一个原子性内置锁,通常也叫做内部锁或者监视器锁。监视器锁是一个排他锁,也就是当一个线程获得监视器锁后,其他线程会被阻塞等待,直到该线程释放监视器锁后才能获取锁。监视器锁在synchronized代码块正常退出、抛出异常或者调用wait系列方法时释放。Java线程和操作系统原生线程一一对应,阻塞一个线程是需要从用户态切换到内核态,所以synchronized的使用会导致上下文切换并带来线程调度开销。

synchronized的内存语义是,进入synchronized代码块时把synchronized代码块里面用到的共享变量在线程的工作内存中清除,这样使用到synchronized块里面的变量时就不会从工作内存里面取。退出synchronized块时,把在工作内存里对共享变量的修改刷新到主内存。从而解决共享变量内存不可见问题。

4.4 volatile关键字解决内存不可见问题

上文介绍了使用synchronized关键字通过加锁释放锁可以解决共享变量的内存不可见问题,但是使用synchronized关键字会带来线程上下文切换的开销。除了synchronized关键字外Java还提供了一种弱形式的同步,也就是使用volatile关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为volatile时,线程在写入变量时不会把变量缓存在寄存器或其他地方,而是会把变量直接刷新会主内存。当其他变量读取该共享变量时,会从主内存重新获取最新值,而不是从当前线程的工作内存中取。

volatile的内存语义是,当线程写入volatile变量时,相当于线程退出synchronized代码块,把共享变量的值刷新回主内存;当线程读取volatile变量时,相当于进入synchronized代码块,先把该变量从工作内存里面清除,然后从主内存中读取最新值。

4.5 synchronized和volatile的区别

synchronized会获取锁和释放锁,synchronized代码块会导致线程阻塞,会造成线程上下文切换和线程重新调度的开销;volatile是非阻塞算法,不会造成线程上下文的开销。

synchronized关键字解决共享变量内存不可见问题和操作的原子性操作,volatile解决了共享变量不可见问题,但不保证操作的原子性。

五、Java原子性操作

所谓原子性操作是指,执行一系列操作时,这些操作要么全部执行,要么全部不执行,不存在只执行其中一部分的情况。如果不能保证操作的原子性操作就可能会出现线程安全问题。

Java中有一个典型的非原子性操作是自增(自减)操作,++i。

一个++i操作是由如下步骤完成的:

  • 读取i的值
  • 将i的值加1
  • 将加1后的结果赋值给i

这不是一个原子操作,如果不能保证三个步骤的原子性,那就可能出现线程安全问题。

5.1 使用synchronized关键字保证操作原子性

通过使用synchronized关键字可以保证内存可见性和原子性,从而实现线程安全。

5.2 使用CAS操作保证原子性

CAS(Compare and Swap)是JDK提供的非阻塞原子性操作,它通过硬件保证比较-更新操作的原子性。JDK里面的Unsafe类提供了一系列的CAS操作。

六、Java指令重排

Java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。当线程下,重排序可以保证最终执行结果和程序顺序执行结果一致,但在多线程下就会存在问题。

6.1 通过volatile避免指令重排序问题

通过把变量声明为volatile可以避免指令重排序问题。

写volatile变量时可以保证volatile写之前的操作不会被编译器重排序到volatile写之后。读volatile变量时可以保证volatile读之后的操作不会被编译器重排序到volatile写之前。

标签:count,Java,变量,synchronized,安全,线程,内存
From: https://www.cnblogs.com/yourblog/p/17873597.html

相关文章

  • Java应用导致CPU使用率过高的排查方法
    1、搭建CentOS7.9,部署JDK8:2、编写测试代码Test.java:publicclassTest{publicstaticvoidmain(String[]args){System.out.println("测试死循环对CPU的影响");while(true){}}}3、编译Test.java:#javacTest.java4、运行Test程序:#jav......
  • Linux配置Java环境变量(详细步骤总结
    (目录)前言Java的环境变量的配置应该是每个java开发者使用Linux必备的一个配置,鉴于之前笔者在配置虚拟机或者云服务器的时候,都需要额外从网页上寻找资料,略显得有点麻烦,故在此总结一篇Java环境变量的详细配置步骤总结,希望可以帮助广大开发者们提高自己的效率下载JDK官网下载j......
  • 计算机在信息安全中的作用
    计算机在信息安全中的作用目录计算机在信息安全中的作用相关知网论文相关搜索资料null相关知网论文有关(大数据背景下)计算机技术在信息安全的应用袁恭昊.以大数据分析为例谈计算机技术在信息安全中的应用[J].数字通信世界,2018,(06):104.摘要:在互联网时代下,网络信息......
  • 《信息安全系统设计与实现》第十三周学习笔记
    第十四章MYSQL实践mysql简介MySQL是一个开源数据库管理系统,由服务器和客户机组成。在将客户机连接到服务器后,用户可向服务器输入SQL命令,以便创建数据库,删除数据库,存储、组织和检索数据库中的数据。MySQL有广泛的应用。mysql安装利用sudoaptinstallmysql-server命令进行......
  • 《信息安全系统设计与实现》第13周学习笔记
    MySQL简介MySQL是一种开源的关系型数据库管理系统,由瑞典MySQLAB公司开发并维护,后被SunMicrosystems收购,再后被Oracle收购。MySQL以其高性能、稳定可靠、易于使用和广泛支持的特点而闻名。它支持多种操作系统,包括Windows、Linux和MacOS等。MySQL使用标准的SQL语言进行数据的存......
  • 2023-2024-1 20232320 《网络空间安全导论》第四周学习总结
    教材学习内容总结本周进行了《网络空间安全导论》第四章的学习,在这一章中,教材给我们重点阐述了系统安全的原理和结构,以下是我的思维导图:教材学习中的问题和解决过程问题一:如何理解最小惊讶原则?问题一:解决方案:问AI在系统安全原理中,最小惊讶原则是指在设计安全机制时,应尽可能......
  • 《信息安全系统设计与实现》第十三周学习笔记
      第十四章MYSQL实践mysql简介MySQL是一个开源数据库管理系统,由服务器和客户机组成。在将客户机连接到服务器后,用户可向服务器输入SQL命令,以便创建数据库,删除数据库,存储、组织和检索数据库中的数据。MySQL有广泛的应用。mysql安装在ubuntu中使用sudoaptinstallmysql-......
  • 2023-2024-1 20232329易杨文轩《网络空间安全导论》第四周学习
    学期2023-2024-1学号:20232329《#学期2023-2024-1学号20232329《网络》第四周学习总结》教材学习内容总结教材学习中存在的问题和解决过程问题1:如何理解访问控制中三元组原理?问题1解决方案:问题2:PUF函数是什么?问题2解决方案:问题3:DBMS、SQL注入是什么?问题......
  • java.net.ConnectException: Connection timed out: connectjava.net.连接异常:连接超
    因为我有steam++加速器,但这个加速器会清空你的hosts文件,往常出现java.net.ConnectException:Connectiontimedout:connectjava.net.连接异常:连接超时:连接这个问题的时候一般都是hosts文件被清空了。但昨天写作业的时候发现在hosts文件写上ip以后还会报错。这个问题我解决了......
  • Java学习之路(十一)
    Java学习之路(十一)1、常用API1.1、Math(应用)1、Math类概述Math包含执行基本数字运算的方法2、Math中方法的调用方式Math类中无构造方法,但内部的方法都是静态的,则可以通过类名.进行调用3、Math类的常用方法方法名方法名说明publicstaticinta......