java-lock

  1. lock
    1. jvm的四类内存屏障

lock

jdk1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,因此锁变味了四种状态,无锁,偏向锁,轻量级锁,重量级锁。锁可以升级但不能降级

  • 偏向锁:偏向第一个访问锁的线程。
    在运行过程中,同步锁只有一个线程访问,不存在线程竞争,不需要触发同步,这种情况下给线程加一个偏向锁。当线程第二次到达同步代码块,回判断持有锁的线程是否为自己,正常则继续。因为没有释放过锁,并不需要加锁。
    如果运行过程中遇到其他线程抢占,则持有偏向锁的线程会被挂起,jvm会消除他身上的偏向锁,变为轻量级锁(撤销偏向锁的时候会导致stw操作)。一旦有第二个线程加入锁竞争,锁就会升级。
  • 轻量级锁(自旋锁):如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。(自旋锁是轻量级锁的一种典型实现,再用户态下,通过自旋的方式(while),实现类似于加锁的效果。这种锁,会消耗一定cpu资源,但是可以做到最快速度拿到锁)
    一旦锁竞争严重,线程达到最大自旋次数,会将轻量级锁继续升级为重量级锁。
  • 重量级锁:当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起,等待将来被唤醒。在JDK1.6之前,synchronized直接加重量级锁,很明显现在得到了很好的优化。(挂起等待锁是重量级锁的一种典型实现,通过内核态,借助系统提供的锁机制,当出现锁冲突的时候,会牵扯到内核对于线程的调度,使冲突的线程出现挂起(阻塞等待),这种锁,消耗的cpu资源是更少的,但也无法保证第一时间拿到锁)

高并发场景下,不建议使用synchronized

  • 由于并发度比较高,因此 synchronized 一定会升级到重量级锁,但是重量级锁的性能是不太高的,因为线程要阻塞再唤醒,需要用户态和内核态之间切换

  • synchronized 没有读写锁优化

  • synchronized 不能对线程唤醒,也就是你线程如果获取不到锁的话会一直阻塞

在使用 synchronized 的时候,一定要 直接将偏向锁给禁掉 ,因为大多数情况下,偏向锁都需要撤销升级为轻量级锁,而偏向锁的撤销性能是比较差的!

所以如果优化的话,对于第一个点来说,将等待线程阻塞再唤醒,个人感觉优化空间不大

第二个点就是读写锁的优化,读读之间不互斥,大幅度增强 读多写少 场景下的性能!

第三个点就是需要一个 tryLock(timeout) 功能,在指定时间获取不到锁的时候,可以直接将线程超时了,不去拿锁了

** synchrnoized是如何保证可见性的**
为了保证数据的一致性,避免其中一个线程修改变量之后,其他线程看不到变量的更新
在计算机底层中可见性的保证,是通过mesi协议来保证的,也就是保证多个处理器和内存之间数据一致性。两个关键机制flush和refresh,修改了刷新高速缓存中的值,读取时发生变量被更新,从其他的高速缓存读取最新的值更新到自己的高速缓存。
也就是强制去读取最新值以及将最新值刷回主内存,在有内存保障的地方强制线程去执行refresh和flush动作,从而保证数据的一致性。
synchrnoized保证可见性也就是通过内存屏障来保证的。进入同步代码块和退出的时候,都会插入内存屏障。也就是在进入的时候强制执行refresh,退出的时候强制执行flush

jvm的四类内存屏障

loadload:屏障前load先于屏障后load
storestore:屏障前store先于屏障后store
loadstore:屏障前load先于屏障后store
storeload:确保屏障之前的所有内存访问操作(包括Store和Load)完成之后,才执行屏障之后的内存访问操作。全能型屏障,会屏蔽屏障前后所有指令的重排


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。