线程安全
线程安全的定义:当多个线程访问同一个对象,不考虑这些线程的调度和交替执行,也不需要额外的同步,或者在调用方法进行其他协调操作时,调用这个对象可以获得正确的结果,那这个对象就是安全的
如何实现线程安全
- 互斥同步
同步就是一个线程进入监视器(类似于只允许一个线程进入的盒子),其他线程必须等待,直到该线程退出监视器为止。
在实现互斥同步的方式中,最常使用的就是synchronized关键字。synchronized实现同步的基础就是:java中的每一个对象都视为锁。具体表现:
- 普通的同步方法:锁当前实例对象
- 静态同步方法:锁当前类的class对象
- 同步方块:锁synchronized代码块中匹配的对象
synchronized(悲观锁)实现原理:
编译之后,会在同步块的前后生成monitorenter和monitorexit两个字节码指令。两个字节码指令之后有一个reference类型(存在于栈中的局部变量表中,可以根据reference数据,来操作堆上的具体对象)的参数来指明要锁定和解锁的对象。当执行monitorenter指令时,首先会尝试获取对象的锁,对象没有被锁定,或者当前线程已经包含对象的锁,锁计数器+1。若获取对象失败,当前线程就阻塞等待,直到对象锁被另一个线程释放。
synchronized用的锁是存放在对象头里面的,jdk1.6之后锁有四种状态:无锁状态,偏向锁状态,轻量级锁,重量级锁
判断对象的锁的状态,就是通过对象头中的锁状态标志位
除了synchronized关键字,还可以使用juc包下的可重入锁来实现同步。非阻塞同步
因为采用同步会导致其他线程大量进行阻塞,需要不断挂起和唤醒线程,引起很大的性能问题。
非阻塞同步是在需要资源的时候马上发起请求,且马上得到答复,然后继续执行之后的程序,如何得到的不是完整的资源,之后将周期性请求,直到获得所需的资源。
cas方法(乐观锁)无同步方案
线程本地存储:将共享数据的可见范围限制在一个线程中,这样无需同步也能保证线程之间不出现数据争用问题。经常使用的就是ThreadLocal类。
总结来说,引起线程不安全最根本的原因是:线程对于共享数据的更改会引起程序结果错误。总体的解决策略就是:保护共享数据在多线程的情况下,保持正确的取值。
采用cas的问题
- 只能对单个变量进行同步
- 自旋循环时间过长
- ABA问题
当变量经历0-2-0的过程,如果指的改动对线程有影响,中间的过程必须让线程感知到。我们可以给变量值加上版本号,任何线程修改一次变量,版本号也加1。取变量值的时候拿到变量值和版本号,通过版本号就能判断是否有其他线程修改过这个变量。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。