作者 | 井方哥
地址 | https://zhuanlan.zhihu.com/p/31612647
声明 | 本文是 井方哥 原创,已获授权发布,未经原作者允许请勿转载
前言
我们通过前面的学习,已经知道了工作内存和主内存的8大原子操作,以及Java线程的实现方式,和状态关系。我们清楚:
高效并发首先要保证并发的准备性,而后才是高效。
本篇还解答了如下问题:
什么是线程安全?
实现线程安全有哪些方法?
锁优化有哪些方案?
线程安全
1、定义
多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替,也不需要额外的同步,或者调用方进行其他的协同操作,调用这个方法的行为最终都能够获得正确的结果,那这个对象是线程安全的。
2、Java中线程安全分类
不可变:
final(未发生this逃逸)
java.lang.String:进行subString()、replace()、concat()不会影响原来的值,而是构建出新的一个不可变的值并返回;
绝对线程安全:不管运行环境如何,调用者都不需要进行额外的同步措施,线程安全不一定就是真的安全;
相对线程安全:不需要做额外的同步,但是特定的连续调用顺序的情况下需要同步处理,才能保证调用的结果准确,如Vector\HashTable\Collections的synchronizedCollection()方法包装的集合等;
线程兼容:本身不安全,通过同步处理能够达到安全的效果;Arraylist和HashMap等;
线程对立:无论采取什么同步措施,都不能达到安全效果。如Theard类的supspend()和resume()这两个已被放弃的方法;
线程安全实现方法
1、互斥同步
原则:保证共享数据在同一时刻只能被一个线程访问
互斥是方法,同步时目的
synchronized:对于同一条线程来说是可重入的,不会出现把自己锁死的情况
java.util.concurrent:重入锁ReentrantLock
等待可中断:持锁线程一直没有释放,等待的线程可以放弃等待
公平锁:多个线程等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁
绑定多个条件:ReentrantLock 调用多次newCondition,绑定多个Condition对象可以实现
优先考虑synchronized,性能差不多。
2、非阻塞同步
原则:不把线程挂起,有错误采取补偿措施;
3、无同步
保证线程安全,不一定要同步;
天生线程安全的代码:
可重入代码:如果一个方法,他的计算结果是可以预测的
线程本地存储:java.lang.ThreadLocal
锁优化
1、自旋锁和自适应自旋
自旋锁:让线程执行一个忙循环(实际让他瞎忙活一会)
自适应自旋:自旋时间有上一次同一个锁自旋时间和锁的拥有者状态来决定
2、锁消除
虚拟机即时编译时发现有些声明了锁,但是实际上没有用的锁(不存在共享数据竞争),直接进行消除
3、锁粗化
本来是锁的范围越小越好,但是对于里面有反复加锁解锁的情况,那就把范围扩大。
4、轻量级锁
目的:没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗
5、偏向锁
锁会偏向于第一个获得它的线程
当有另外的线程尝试获取时,偏向模式宣告结束
小结
线程安全是说在没有额外或者协同的同步处理的情况下,多线程操作共享数据,依然可以得到准备的结果。Java开发中主要通过互斥同步的方案来达到线程安全的目的,其中用到的Synchronized和ReentrantLock。还有记住ThreadLocal可以给线程存储私有的信息。我们在进行同步的时候是通过锁 来保证的,对于有些没有必要的锁,我们通过一些手段进行优化,进来减少锁带来的性能开销。
说明: 本系列多处摘抄《深入理解Java虚拟机》中内容,主要精简了本书的要点,并叙述自己对本书的理解。本人才疏学浅,文章中有不对的地方,还望批评指教。
本系列
Android 精进:简述 JVM 基础(三):垃圾收集器与内存分配策略