更多请看:synchronized 原理
java当中的synchronized用的是悲观锁
使用方式及作用对象
- 同步普通方法,锁的是当前对象。
- 同步静态方法,锁的是当前
Class
对象。 - 同步块,锁的是
()
中的对象。
实现原理
JVM
是通过进入、退出对象监视器(Monitor
)来实现对方法、同步块的同步的。
具体实现是在编译之后在同步方法调用前加入一个monitor.enter
指令,在退出方法和异常处插入monitor.exit
的指令。
其本质就是对一个对象监视器(Monitor
)进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程monitor.exit
之后才能尝试继续获取锁。
锁优化
JDK1.6
中对 synchronized
进行了各种优化,为了能减少获取和释放锁带来的消耗引入了偏向锁
和轻量锁
。
轻量锁
当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(Lock Record
)区域,同时将锁对象的对象头中Mark Word
拷贝到锁记录中,再尝试使用CAS
将Mark Word
更新为指向锁记录的指针。
如果更新成功,当前线程就获得了锁。
如果更新失败JVM
会先检查锁对象的Mark Word
是否指向当前线程的锁记录。
如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。
不是则说明有其他线程抢占了锁
如果存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁。
偏向锁
偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。
适应性自旋
在使用 CAS
时,如果操作失败,CAS
会自旋再次尝试。由于自旋是需要消耗 CPU
资源的,所以如果长期自旋就白白浪费了 CPU
。JDK1.6以后对自旋进行了优化:如果某个锁自旋很少成功获得,那么下一次就会减少自旋。
CAS
乐观锁的核心算法是CAS(campare and swap 比较并交换)
实现原理
首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。
CAS是原子性的,这个原子性是由硬件级别的指令来保证的。
ABA问题
假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大,解决的思路是引入版本号,每次变量更新都把版本号加一。
悲观锁和乐观锁
悲观锁
总是假设最坏的情况,认为每次取数据时数据都会被其他线程所修改,所以都会加锁(行锁,表锁等),当其他线程想要读取数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等。(synchronized的思想也是悲观锁)
乐观锁
总是认为不会有并发问题,认为每次取数据时数据不会被其他线程更改,所以不会加锁,但是在更新之前会判断数据有没有被其他线程修改,一般通过版本号或CAS操作实现
总之:读取频繁使用乐观锁,写入频繁使用悲观锁。