啃碎并发五:Java线程安全特性与问题
前言
共享、可变
1.线程安全特性
原子性、可见性、有序性
2.线程安全问题
存在 竞态条件与临界区 死锁:互斥条件、请求和保持条件、不可剥夺条件、环路等待 活锁:互相礼让 饥饿:cpu是资源,某个线程因为其他线程的抢占一直吃不到资源(ReentrantLock不公平锁) 1. 高优先级线程吞噬所有的低优先级线程的CPU时间 2. 其他线程总是能持续第先于某个线程获得对同步代码区(synchronized)的访问 3. 多个线程执行wait(),notify()不保证其中一个线程一定被唤醒 公平:公平锁,通过队列的方式,所有线程对应一个QueueObject对象,unlock是唤醒队列中第一个。
3.确保线程安全
- 如何确保原子性:
- 锁和同步(通过锁或同步来实现资源的排它性)--基于操作系统
- CAS:基础类型变量自增 --基于CPU级别CAS指令
- 如何确保可见性:
- volatile:对该变量的修改会被立即更新到内存中,同时将其他线程中缓存的该变量置为无效 应用场景:线程状态标志位,blog.csdn.net/vking_wang/…
- 如何保证有序性
- volatile、synchronized和锁
- happens-before原则
4.volatile、synchronized和锁 小结
- 锁和synchronized即保证了原子性也保证了可见性(同时只有一个线程执行目标代码)
- 基于happens-befor,一个线程的写操作对另一个线程的读操作可见。
- 可见性:synchronized和锁基于操作系统裁决,开销高;volatile开销小
- AutomicInteger基于CPU cas,开销最小
- 避免使用共享变量
啃碎并发六:Java线程同步与实现
java中提供的线程同步操作:synchronized关键字、wait/notifyAll、ReentrantLock、Condition、并发包下的工具类、Semaphore、ThreadLocal、AbstractQueuedSynchronizer ReentrantLock:对于同一个线程,可以继续调用加锁的方法,而不会被挂起 synchronized:也支持重入(同步一般没必要同步整个方法,只同步关键代码即可) Condition条件对象:Condition是绑定到Lock上的(lock.newCondition()),可以为多个线程间建立不同的Condition,提供了await()替代wait(),signal()替代notify(),signalAll()替代notifyAll() wait/notifyAll:调用时一定要获得当前线程的锁 ThreadLocal:把变量放到线程本地的方式来实现线程同步。ThreadLocal, 主要针对线程不安全的类或对象 volatile修饰变量:不能用final类型再次修饰 Semaphore信号量:用于控制特定资源在同一个时间被访问的个数。类似连接池
并发包下的工具类
CountDownLatch:计数器,先是countDownLatch.await()一直等待,然后线程内countDownLatch.countDown()计数器减到0,一并执行。(准备,倒计时后一起跑,起点一致) CyclicBarrier:阻塞计数到达后,一起解锁。比CountDownLatch更灵活;
原子变量操作:AtomicInteger.addAndGet()
AbstractQueuedSynchronizer: AQS是很多同步工具类的基础。
使用阻塞对垒实现线程同步:LinkedBlockingQueue
啃碎并发七:深入分析Synchronized原理
- Synchronized可以把任务非null对象作为锁,hotSpotJVM中,锁叫做 对象监视器ObjectMonitor
- 可以实现对临界资源的同步互斥访问,是可重入的(最大作用是避免死锁),如子类同步方法又调用了父类同步方法的场景,如果没有重入特性,则会发生死锁。
- monitorenter: 每个对象都是一个监视器锁,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,所以这些方法只能在同步块或者同步方法中调用。
同步概念
- Mark Word:标记字段,用于存储对象自身的运行时数据,哈希码,GC分代年龄,锁状态标志位,线程持有的锁,偏向线程ID,偏向时间戳等。
参考文献
-
啃碎并发六:juejin.cn/post/684490…
-
啃碎并发七:juejin.cn/post/684490…