线程安全详解

  1. 线程安全
    1. 如何实现线程安全
    2. 采用cas的问题

线程安全

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

如何实现线程安全

  • 互斥同步
    同步就是一个线程进入监视器(类似于只允许一个线程进入的盒子),其他线程必须等待,直到该线程退出监视器为止。
    在实现互斥同步的方式中,最常使用的就是synchronized关键字。synchronized实现同步的基础就是:java中的每一个对象都视为锁。具体表现:
  1. 普通的同步方法:锁当前实例对象
  2. 静态同步方法:锁当前类的class对象
  3. 同步方块:锁synchronized代码块中匹配的对象
    synchronized(悲观锁)实现原理:
  • 编译之后,会在同步块的前后生成monitorenter和monitorexit两个字节码指令。两个字节码指令之后有一个reference类型(存在于栈中的局部变量表中,可以根据reference数据,来操作堆上的具体对象)的参数来指明要锁定和解锁的对象。当执行monitorenter指令时,首先会尝试获取对象的锁,对象没有被锁定,或者当前线程已经包含对象的锁,锁计数器+1。若获取对象失败,当前线程就阻塞等待,直到对象锁被另一个线程释放。

  • synchronized用的锁是存放在对象头里面的,jdk1.6之后锁有四种状态:无锁状态,偏向锁状态,轻量级锁,重量级锁
    判断对象的锁的状态,就是通过对象头中的锁状态标志位
    除了synchronized关键字,还可以使用juc包下的可重入锁来实现同步。

  • 非阻塞同步
    因为采用同步会导致其他线程大量进行阻塞,需要不断挂起和唤醒线程,引起很大的性能问题。
    非阻塞同步是在需要资源的时候马上发起请求,且马上得到答复,然后继续执行之后的程序,如何得到的不是完整的资源,之后将周期性请求,直到获得所需的资源。
    cas方法(乐观锁)

  • 无同步方案
    线程本地存储:将共享数据的可见范围限制在一个线程中,这样无需同步也能保证线程之间不出现数据争用问题。经常使用的就是ThreadLocal类。

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

采用cas的问题

  1. 只能对单个变量进行同步
  2. 自旋循环时间过长
  3. ABA问题
    当变量经历0-2-0的过程,如果指的改动对线程有影响,中间的过程必须让线程感知到。我们可以给变量值加上版本号,任何线程修改一次变量,版本号也加1。取变量值的时候拿到变量值和版本号,通过版本号就能判断是否有其他线程修改过这个变量。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。