java线程安全和同步锁介绍
线程安全的五种类型
1、不可变
共享的数据是基本数据类型,就不需要考虑线程安全性问题。共享的是对象就需要关注对象行为不会改变状态。
2、绝对线程安全
3、相对线程安全
特定顺序的连续调用,可能需要在调用端使用额外的同步手段来保证调用的正确性。
4、线程兼容
对象本身不是线程安全,可以使用同步手段保证线程安全
5、线程独立
无能怎么使用都无法在多线程中使用的代码。
线程安全的实现方法
1、互斥同步
sychronized修饰的是实例方法还是类方法来确定,锁对象是实例对象还是类对象。对同一个线程是可以重入的,不会出现自己锁死自己的情况。java线程是映射到原生线程上,因此阻塞和唤醒都需要
操作系统来帮忙,需要从用户态转换成核心态需要耗费很多处理器时间,因此sychronized是java语言中的一个重量级操作。虚拟机也做了一些优化例如在操作系统阻塞线程之前加入了一些自旋操作,
避免频繁进入核心态。
ReentrantLock,1.6后sychronized性能和它基本一致
1)等待可中断,正在等待的线程,可以选择放弃等待,去做其他的事情。
2)公平锁,按照等待时间长短,顺序依次获取锁。非公平锁是释放时自己竞争。
3)绑定多个条件
2、非阻塞同步
互斥同步的主要问题,线程阻塞和唤醒带来的性能问题。互斥同步都属于悲观的并发策略。认为共享数据只要不做正确同步就一定会出现问题,无论共享数据是否会发生竞争,都需要加锁,用户态核心转
转换,维护锁计数器,检查是否有线程被阻塞需要唤醒等操作。硬件指令集发展,基于冲突检查的乐观锁机制,先进行操作,没有冲突就成功,有冲突就进行补偿操作被称为非阻塞同步。因为需要操作和冲突检测这两个步骤需要原子性,所以只有硬件指令集支持才能进行。CAS指令集,需要三个值V版本,A旧值,B新值。当且仅当v版本一致时就用B值更新A值。
可重入代码,就是代码可以在任何地方中断去执行其他代码,控制权回来后,原来的程序不会有任何错误。不依赖堆上数据和公用的系统资源。
3、线程本地存储
Web交互模型中,一个线程对应一个请求,线程本地存储广泛用来解决线程安全问题。
锁优化
1、自旋锁,共享数据锁定一般只有一小会,为了这一小会去挂起和唤醒线程不值得,因此一般采用自旋操作进行等待。
2、锁消除,编译期间发现不可能产生数据竞争,进行锁消除。主要是逃逸分析,所有的堆上的数据对象都不会被其他线程访问,可以当做栈上数据操作对待。
3、锁粗化,锁如果出现在循环块中可以只加一次锁。
4、轻量级锁,相对于使用操作系统中互斥量来实现的传统锁,传统锁就是重量级锁。如果同步对象锁定,虚拟机将首先在当前线程的栈帧中建立一个锁记录空间,用于存储对象目前markword的拷贝,然后虚拟机通过cas操作尝试设置对象markword锁标志位,此时处于轻量级锁。如果更新操作失败,先检查线程锁记录空间查看是否有当前锁,有进入执行,无说明对象被其他线程锁定。两个以上线程争抢用一把锁,就膨胀为重量级锁,后面等待锁的线程进入阻塞状态。解锁也是CAS操作进行,将对象markword的displace值替换回来,如果替换失败说明有其他线程正在尝试获取锁。提升性能经验,对于绝大部分锁,在同一时间不会产生竞争,使用轻量级锁可以避免互斥量的开销。如果存在竞争有cas操作和互斥量开销,轻量级锁比重量级锁开销更大。
5、偏向锁就是在无竞争的情况下,连CAS都不做。当锁对象第一次被线程获取的时候,虚拟机将锁头设置为01,偏向模式同时采用cas将线程ID记录到对象markword中,cas成功后续进入这个锁相关的代码时,虚拟机不需要任何同步操作。当另外一个线程尝试获取这个锁时,偏向模式结束,根据锁对象目前是否锁定,撤销偏向后恢复到未锁定或者轻量级锁状态。如果锁总是被多个线程访问,那么偏向锁就是多余的。