怎样才是线程安全的?
一、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 操作消除同步使用的互斥量。
3、偏向锁
无竞争情况下,消除整个同步块。偏向于第一个获得锁的线程。
4、锁粗化
选择合适的粒度层级进行加锁同步。
5、锁消除
如果一段代码中,堆上的所有数据都不会逃逸出去而被其它线程访问到(逃逸分析),当作栈上数据对待,消除锁。
等等。
五、附加订阅