数据竞争:当多个线程同时修改一个共享数据时,导致的并发bug(其实就是线程安全性)
public class Test { private long count = 0; void add10K() { int idx = 0; while(idx++ < 10000) { count += 1; } } }竞态条件:指的是线程执行的结果依赖线程执行的顺序
public class Test { private long count = 0; synchronized long get(){ return count; } synchronized void set(long v){ count = v; } void add10K() { int idx = 0; while(idx++ < 10000) { set(get()+1) //当两个线程同时运行到get()方法时,get()方法先后(有sync锁)0,count结果为1。当先后执行时,count结果为2 } } }解决方案 -- 互斥(锁)
CPU 提供了相关的互斥指令,操作系统、编程语言也会提供相关的 API。从逻辑上来看,我们可以统一归为:锁
活跃性问题
所谓活跃性问题,指的是某个操作无法执行下去。我们常见的“死锁”就是一种典型的活跃性问题,当然除了死锁外,还有两种情况,分别是“活锁”和“饥饿”
活锁:互相“谦让”的例子。线程因为总是同时的进行竞争而导致的互相等待的现象。
解决方案:让线程等待一个随机的时间。避免“同时”即可
死锁:一组线程线程因为竞争共享数据而陷入永久性等待,导致线程“永久”的阻塞。
解决方案:破坏四个条件即可:互斥、占用且等待、不可抢占、循环等待
饥饿:所谓“饥饿”指的是线程因为无法访问所需资源而无法执行下去的情况
解决方案
公平的分配资源
保证资源充足
避免线程长时间持有锁
公平锁,先来先得
性能问题
尽量采用无锁方式 乐观锁(CAS) 本地化存储(ThreadLocal) copy-on-write
尽量减少线程持有锁的时间
优化锁粒度 联想1.8前后ConcurrentHashMap的锁设计
总结
安全性方面注意数据竞争 竞态条件问题
活跃性方面注意死锁 活锁 饥饿等问题
性能方面尽量采用无锁CAS,优化锁粒度,减少锁持有时间
08 | 管程:并发编程的万能钥匙笔记
管程和信号量
管程和信号量是同步的,所谓同步也就是管程能实现信号量,信号量也能实现管程
什么是管程
管程是一个概念
在java中每一个对象都绑定着一个管程(信号量)
线程访问加锁对象其实就是去拥有一个监视器的过程。
线程访问共享变量的过程其实就是申请拥有监视器的过程。
监视器至少有两个等待队列。
总结起来就是,管程就是一个对象监视器。任何线程想要访问该资源,就要排队进入监控范围。进入之后,接受检查,不符合条件,则要继续等待,直到被通知,然后继续进入监视器。
java中的管程
1.5之前:①synchronized + wait、notify、notifyAll
1.5之后:②lock + condition
区别
①只支持一种一个条件变量,即wait,调用wait时将会将其加入到等待队列。被notify时会随机通知一个线程加入到锁的等待池
②相对①condition支持中断和增加了等待时间
三种实现管程的模型
HASEN:执行完,再去唤醒另外一个线程。能够保证线程的执行
HOARE:是中断当前线程,唤醒另外一个线程,执行玩再去唤醒,也能够保证完成。
MESA:是进入等待队列,不一定有机会能够执行(公平竞争公平 == 容易饥饿)
09 | Java线程(上):Java线程的生命周期笔记
通用的五种线程状态
初始状态
可运行状态
运行状态
休眠状态
终止状态
java线程的五种状态
new(初始化)
runnable(可运行/运行状态)
blocked(阻塞状态)
waiting(无限时等待)
timed_waiting(有时限等待)
terminated(终止)
RUNNABLE 与 BLOCKED 的状态转换