为什么需要并发编程?
-
并发编程会大大提高系统复杂度,为什么还需要它? 答:要想充分发挥多处理器系统的强大计算能力,最简单的方式就是使用线程
-
线程带来的风险
-
安全性问题
@NotThreadSafe public class UnsafeSequence{ private int value;/**返回一个唯一的数值。*/ public int getNext(){return value++;} } -
活跃性问题
- 死锁
- 活锁
- 饥饿
-
性能问题
- 上下文切换
- 同步机制使得缓存行失效、抑制编译器优化
-
-
线程安全性:保证共享和可变状态变量的正确性
- 不在线程间共享该变量(ThreadLocal)
- 使用不可变变量(Final)
- 同步机制(synchroized、CAS、voliate)
-
无状态对象一定线程安全:不包含任务域或者对其他类中域的应用,其实就是利用栈封闭的思想获得线程安全
-
竞态条件:最典型的就是先检查后执行,这种保证不了原子性从而造成线程安全性问题
-
复合操作:i++
-
java.util.concurrent.atomic
-
LongAdder、DoubleAdder:
LongAdder的思想是将一个value打散成多个Cell数组,每个Cell维护一个value,当存在多线程并发操作的时候,将线程映射到一个Cell上进行操作,每个Cell内的value值还是使用CAS更新,就这样将竞争分散到多个Cell中。同时,如果没有出现多线程竞争,那么直接操作提供的一个base字段,换句话说就是先通过CAS修改base字段,如果CAS失败,那么转去修改Cell。最后获取值的时候,即使把base和所有cell相加。\\
-
内置锁:synchroized原理
作用
- 原子性:确保线程互斥的访问同步代码;
- 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中,如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的
- 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”
在HotSpot JVM实现中,锁有个专门的名字:对象监视器(Object Monitor) 。
Synchronized总共有三种用法:
- 当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
- 当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
- 当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;
可重入:避免死锁->子类同步方法调用了父类同步方法,如没有可重入的特性,则会发生死锁;
反编译:javap -v Demo1.class