怎样才是线程安全的?

一、final 一下

final 关键字,具有不可变性。所以并发中对于其的使用时线程安全的。

final 可以修饰变量,方法及类对象。

不可变:不可被修改,不可被继承,不可被覆盖,重写等。

二、加锁

锁的本质就是一个同步块,处于同步块儿中的操作可以认为是线程安全的。

那怎么加锁呢?

1、synchronized 关键字

底层其实对应的是用 monitorenter 及 monitorexit 字节码指令包裹需要同步的执行。

monitorenter

... ...

monitorexit 

同一个线程对同一个同步块的加锁,只会执行一次,当获取到锁后,再次进入同步块时,则不需要再次进行加锁,我们称之为“可重入”,当然,重入也会被记录,

线程对对象的加锁标识,存在于对象的头信息中,具体可以了解下:偏向锁、轻量锁、重量所,锁膨胀相关知识。

对于静态执行的锁:锁类对象。

对于非静态执行的锁:锁类实例。

2、lock

我们称之为可重入锁。

可重入,也就是我们上面所说的,在获锁期间,可以重复进入。

相对于synchronized来说,lock更加灵活:可中断,可超时,公平锁等。

lock 的实现基于 AQS

基本使用:

try {

lock();

... ...

} finally {

    unlock();

}

特别需要注意的一点是一定要保证 unlock。

三、局部变量

局部变量,其实实操对应的是线程私有栈中的变量操作。因为私有,所以是线程安全的。

线程栈具体参考:Java虚拟机栈 

局部变量以栈帧的形态参与实际的运行计算。

四、锁优化

1、CAS自旋

CAS:Compare And Swap 对比然后交换值,CAS 属于硬件指令级别,因此本身可以保证原子性。

自旋:CAS 操作中会有个期望值的限制,达不到这个预期,则执行不成功。因此期望最终能够执行,则需要多次尝试,也就是所谓的“自旋”,循环尝试。

例如,对于普通的“++”操作,是非线程安全的,而要达到线程安全的+1目的,就必须添加必要的同步性保障,或者,使用此处我们所论述的 CAS 特性。

示例:AtomicInteger::getAndIncrement

内部通过 Unsafe 实例的 compareAndSwapInt 方法实现。

2、轻量锁

单一线程访问加锁优化;对象头信息 Mark Word;前提--- 对于绝大多数锁,在整个同步周期内是不存在竞争的;无竞争情况下,使用 CAS 操作消除同步使用的互斥量。

执行过程:
 
进入同步块,对象未加锁(锁标志位 01
|
线程栈帧中建立一个锁记录(Lock Record)空间(存储锁对象Mark Word 拷贝:Displaced Mark Word)
|
虚拟机尝试使用 CAS 操作,将对象的 Mark Word 更新为指向 Lock Reord 的指针
|
更新成功,线程拥有对象锁,对象Mark Word锁标志位变为 00
|
更新失败,则检查对象 Mark Word 是否指向当前线程
|
是的话,则说明已经拥有了轻量级锁,继续同步块操作
|
否的话,则说明对象已经被其它线程占有,两个及以上线程征用同一个锁,则膨胀为重量级锁,所标志位变为10,Mark Word 存储的是指向重量级锁()的指针
 
解锁:CAS 将线程的复制的 Displace Mark Word 和对象的 Mark World 替换回来。

3、偏向锁

无竞争情况下,消除整个同步块。偏向于第一个获得锁的线程。

对象锁第一次被线程获取的时候,虚拟机把对象头的标志位设为01,偏向模式,同时使用 CAS 模式把获取到这个锁的线程ID写入对象的 Mark Word 中
|
获取锁成功,持有偏向锁的线程每次进入锁的相关同步块,虚拟机都可以不再进行任何同步操作。
|
另外线程尝试获取此所,则偏向模式结束,撤下偏向(Revoke Bias),恢复未锁定(00)或者轻量级锁定(10)。

4、锁粗化

选择合适的粒度层级进行加锁同步。

5、锁消除

如果一段代码中,堆上的所有数据都不会逃逸出去而被其它线程访问到(逃逸分析),当作栈上数据对待,消除锁。

等等。

五、附加订阅

 

posted @ 2020-05-10 15:09  WindWant  阅读(359)  评论(0编辑  收藏  举报
文章精选列表