什么是线程安全

参考:

《Java并发编程实践》中对线程安全的定义:

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

《深入理解Java虚拟机》的作者也认可这个观点。本人也认为这是一个恰当的定义,因为线程安全的主体是什么?是方法还是代码块?这里给出的主体是对象,这是非常恰当的,因为Java是纯面向对象的,Java中一切为对象。因此通过对象定义线程安全是恰当的。

但是,这里并不是说其他的方式定义不对(这里绝没有这个意思)。我们可以看一下其他的定义方式,进行一下对比:

当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类时线程安全的。
如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的

多线程编程中的三个核心概念

  • 原子性

这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

关于原子性,一个非常经典的例子就是银行转账问题:比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作继续——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。

  • 可见性

可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略
或者理解错误的一点。

CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。

这一点是操作系统或者说是硬件层面的机制,所以很多应用开发人员经常会忽略。

  • 有序性

顺序性指的是,程序执行的顺序按照代码的先后顺序执行。

以下面这段代码为例

boolean started = false; // 语句1
long counter = 0L; // 语句2
counter = 1; // 语句3
started = true; // 语句4

从代码顺序上看,上面四条语句应该依次执行,但实际上JVM真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。

处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。

讲到这里,有人要着急了——什么,CPU不按照我的代码顺序执行代码,那怎么保证得到我们想要的效果呢?实际上,大家大可放心,CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果和代码顺序执行时的结果一致。

如何实现线程安全

  • 第一种 : 互斥同步

何谓同步?在多线程编程中,同步就是一个线程进入监视器(可以认为是一个只允许一个线程进入的盒子),其他线程必须等待,直到那个线程退出监视器为止。

在实现互斥同步的方式中,最常使用的就是Synchronized 关键字。

synchronized实现同步的基础就是:Java中的每一个对象都可以作为锁。

具体表现为:1.普通同步方法,锁是当前实例对象

                     2.静态同步方法,锁是当前类的Class对象3.同步方法块,锁是Synchronized括号里匹配的对象
  • 如何实现?

    • synchronized经过编译之后,会在同步块的前后生成 monitorenter 和monitorexit这两个字节码指令。这两个字节码指令之后有一个reference类型(存在于java虚拟机栈的局部变量表中,可以根据reference数据,来操作堆上的具体对象)的参数来指明要锁定和解锁的对象。根据虚拟机规范,在执行monitorenter 指令时,首先会尝试获取对象的锁,如果该对象没有被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加一。若获取对象失败,那当前线程就要阻塞等待,知道对象锁被另一个线程释放。

    • synchronized用的锁是存放在对象头里面的,在jdk1.6之后,锁一共有四种状态:无锁状态,偏向锁状态(在对象头和栈帧中的锁记录里存储偏向锁的线程id),轻量级锁状态(将对象头的mark word复制到当前线程的栈帧的锁记录中,使用CAS操作将对象头中的markWord指向栈帧中的锁记录,如果成功,则线程就拥有了对象的锁。如果两个以上的线程争用一把锁的话,则膨胀为重量级锁),重量级锁状态。

    因为之前我一直都很迷惑,所以我接下来讲一讲这一方面 :

    大家应该都知道,java 在虚拟机中除了线程计数器,java虚拟机栈 是线程私有的,其余的java堆,方法区,和运行时常量池都是线程共享的内存区域。java堆是存储对象和数组的,但是对象在内存中的存储布局可以分为三块区域:对象头,实例数据(对象真正存储的有效信息,程序代码中所定义的各个类型的字段内容),对齐填充。

    为什么说synchronized的锁是存放在对象头里面呢?因为对象头里面也存储了两部分信息:第一部分呢,存储对象自身的运行时数据,包括哈希码,GC分代年龄,锁状态标识位,线程持有的锁,偏向锁Id,偏向时间戳等数据。第二部分是类型指针,虚拟机通过这个来确定该对象是哪个类的实例。

    如何判断该对象有没有被锁?对象头里面锁状态的标志位会发生变化,当其他线程查看synchronized 锁定的对象时,会查看该对象的对象头的标志位有没有发生变化,若标志位为01,则表示未锁定,为00时,则表示轻量级锁定,为10时,则为重量级锁定状态。为01时,则为偏向锁,为11时,则为GC标记状态。

除了synchronized 关键字之外,还可以使用JUC包下的重入锁来实现同步 。

  • 第二种方法就是:非阻塞同步

    因为使用synchronized的时候,只能有一个线程可以获取对象的锁,其他线程就会进入阻塞状态,阻塞状态就会引起线程的挂起和唤醒,会带来很大的性能问题,所以就出现了非阻塞同步的实现方法。

      先进行操作,如果没有其他线程争用共享数据,那么操作就成功了,如果共享数据有争用,就采取补偿措施(不断地重试)。我们想想哈,互斥同步里实现了 操作的原子性(这个操作没有被中断) 和 可见性(对数据进行更改后,会立马写入到内存中,其他线程在使用到这个数据时,会获取到最新的数据),那怎么才能不用同步来实现原子性和可见性呢? CAS是实现非阻塞同步的计算机指令,它有三个操作数:内存位置,旧的预期值,新值,在执行CAS操作时,当且仅当内存地址的值符合旧的预期值的时候,才会用新值来更新内存地址的值,否则就不执行更新。使用方法:使用JUC包下的整数原子类decompareAndSet()和getAndIncrement()方法缺点 :ABA 问题  版本号来解决只能保证一个变量的原子操作,解决办法:使用AtomicReference类来保证对象之间的原子性。可以把多个变量放在一个对象里。
    
  • 第三种:无同步方案

    线程本地存储:将共享数据的可见范围限制在一个线程中。这样无需同步也能保证线程之间不出现数据争用问题。

    经常使用的就是ThreadLocal类

    ThreadLocal类 最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。

      public T get() { }  public void set(T value) { }  public void remove() { }  protected T initialValue() { }  get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法
    

    其实引起线程不安全最根本的原因 就是 :线程对于共享数据的更改会引起程序结果错误。线程安全的解决策略就是:保护共享数据在多线程的情况下,保持正确的取值。

什么是线程安全?如何保证线程安全?相关推荐

  1. 线程、线程匿名内部类、解决线程不安全的方式

    线程 线程:正在运行的程序,是程序的执行路径:多线性 进程:是应用程序的载体,程序运行在虚拟机中.一个应用软件对应一个进程. 一个进程包含多个线程,一个线程对应一个进程. 好处:提高软件的运行效率 多 ...

  2. SQLite第三方框架FMDB的使用,以及使用FMDatabaseQueue保证线程安全

    2019独角兽企业重金招聘Python工程师标准>>> (1)下载地址:https://github.com/ccgus/fmdb (2)注意点 --语句可以带分号":&q ...

  3. 它又来了!C**HashMap是如何保证线程安全的?会用不就完了?

    欢迎关注方志朋的博客,回复"666"获面试宝典 阅读此篇文章,你需要有以下知识基础 Java内存模型,可见性问题 CAS HashMap底层原理 我们知道,在日常开发中使用的Has ...

  4. Java并发,volatile+不可变容器对象能保证线程安全么?!

    <Java并发编程实战>第3章原文 <Java并发编程实战>中3.4.2 示例:使用Volatile类型来发布不可变对象 在前面的UnsafeCachingFactorizer ...

  5. Java并发编程 synchronized保证线程安全的原理

    文章转载致博客 blog.csdn.net/javazejian/- 自己稍加完善. 线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源 ...

  6. 解读Java8中ConcurrentHashMap是如何保证线程安全的

    HashMap是工作中使用频度非常高的一个K-V存储容器.在多线程环境下,使用HashMap是不安全的,可能产生各种非期望的结果. 关于HashMap线程安全问题,可参考笔者的另一篇文章: 深入解读H ...

  7. 多线程下C#如何保证线程安全?

    多线程编程相对于单线程会出现一个特有的问题,就是线程安全的问题.所谓的线程安全,就是如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是 ...

  8. iOS之深入解析保证线程安全的“锁”的使用和性能分析

    一.线程安全 在平时的开发中经常使用到多线程,在使用多线程的过程中,难免会遇到资源竞争的问题,那么怎么来避免出现这种问题呢? 当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕. ...

  9. MyBatis(六)SqlSessionTemplate是如何保证线程安全的

    前面说到DefaultSqlSession不是线程安全的,所以在MyBatis和spring项目整合的时候不能直接使用DefaultSqlSession,而是自己封装了一个线程安全的SqlSessio ...

  10. shiro如何保证session不失效_请问在不加锁的情况下如何保证线程安全?

    概念 compare and swap,解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数--内存位置(V).预期原值(A)和新值(B).如果内存位置的值与预期原值相匹配,那么 ...

最新文章

  1. Case When 解决简单的是与否
  2. 一起睡了四年的大学舍友
  3. mysql 读写分离中间层
  4. Android网络编程
  5. Lumen开发:如何向 IoC 容器中添加自己定义的类
  6. Spring系列合并
  7. 信息学奥赛C++语言:数字反转
  8. Linux 3.13.0删除了Netlink API函数genl_register_ops() / genl_unregister_ops()
  9. springmvc使用rest风格的404错误
  10. 基于PHP+MySQL共享自行车租赁管理系统的设计与实现
  11. 强化学习论文分析3---蜂窝网络联合频谱和功率分配的深度强化学习--《Deep Reinforcement Learning for ......》
  12. Softing物联网(IoT)方案之OT/IT数据集成
  13. Layer-2方案StarkWare估值80亿 以太坊扩容方案重要角色
  14. 最大的计算机计数单位,计数单位最大只知道亿?也许更大的计数单位能让你笑出声。...
  15. 支付宝转账银行卡收款二维码制作教程
  16. BackTrack5 学习笔记5 扫描 Nmap
  17. 怎么解决缺少java.doc_阿里代码规范检测中方法缺少javadoc注释怎么办
  18. 【机器学习】adaboost算法
  19. 高德地图与CAD图叠加显示方法汇总及优缺点分析
  20. 御泥坊医用冷敷贴php,医用冷敷贴可以做面膜吗 医用冷敷贴能当做面膜用吗

热门文章

  1. 浏览器API 文字转语音
  2. 电脑耳机没声音怎么设置?Windows系统适用
  3. 支付宝小程序 alipays协议链接跳转
  4. Office 2007 默认打开双页显示变单页显示
  5. 取消word文档中某些页面的页眉
  6. MATLAB图像处理实验——细胞图像的分割和计数
  7. Chrome 复制网页不可复制的文字
  8. 钢铁企业的能耗管理现状和管理建议
  9. @开源镜像站(linux系统:Center OS|Ubuntu|Debian)
  10. winrm java客户端_winrm service