线程并发安全问题解决方案
目录
问题
并发安全是多个线程对共享资源进行访问操作,共享数据能被正确处理,那么如何保证线程的并发安全呢?
解决方案
保证并发安全的三大特性
1 原子性
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉
例如i=i+1不是原子操作,此时并发调用该指令时,i值不会如我们预想那样进行赋值,这便是原子性问题
2 可见性
可见性是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改
可参考4.1 JMM抽象内存模型,主存与工作内存不一致,便会引起可见性问题
3 有序性
为了性能优化,编译器和处理器会进行指令重排序;也就是说java程序天然的有序性可以总结为:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。
1、synchronized
保证原子性、可见性、有序性
synchronized通过对象的对象头中的MarkWord实现,其锁升级过程步骤如下
1.初期锁对象刚创建时,还没有任何线程来竞争,对象的Mark Word是下图的第一种情形,这偏向锁标识位是0,锁状态01,说明该对象处于无锁状态(无线程竞争它)。
2.当有一个线程来竞争锁时,先用偏向锁,表示锁对象偏爱这个线程,这个线程要执行这个锁关联的任何代码,不需要再做任何检查和切换,这种竞争不激烈的情况下,效率非常高。这时Mark Word会记录自己偏爱的线程的ID,把该线程当做自己的熟人。如下图第二种情形。
3.当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,通过CAS对对象头进行设置,哪个线程先占有锁对象并执行代码,锁对象的Mark Word就执行哪个线程的栈帧中的锁记录。如下图第三种情形。
4.如果竞争的这个锁对象的线程更多,导致了更多的切换和等待,JVM会把该锁对象的锁升级为重量级锁,这个就叫做同步锁,这个锁对象Mark Word再次发生变化,会指向一个监视器对象,这个监视器对象用集合的形式,来登记和管理排队的线程。如下图第四种情形。
2、Lock
3、CAS
4、volatile
用于保证变量的可见性与有序性,不保证原子性。
使用volatile修饰的变量,虚拟机将不会对指令重排序,同时值的读写将直接对主内存进行操作。
5、JDK并发工具
5.1、CyclicBarrier
5.2、CountDownLatch
5.3、Semaphore
6、并发安全的集合
6.1、List
6.2、Map
6.3、Set
6.4、Queue