synchronized

并发安全问题

在多线程情况下,多个线程同时访问一个共享的可变的资源时,会发生线程安全问题。

序列化访问临界资源是解决所有线程安全类问题的方案,在同一时刻,只能有一个线程访问这些临界资源,也叫同步互斥访问

Java提供synchronizedLock两种方式来实现同步互斥访问。

方法内部的局部变量不是临界资源,因为这些变量只能由当前线程自己访问,不具有共享性,所以不会发生线程安全问题。

synchronized原理

synchronized是一种对象锁(不是对引用加锁),作用粒度是对象,是可重入锁 。
加锁方式:

  • 同步实例方法,加锁当前实例对象;

    public synchronized void m1(){}
    
  • 同步类方法(静态方法),加锁当前Class类对象;

    public static synchronized void m2(){}
    
  • 同步代码块,加锁括号里面的对象;

    Object object = new Object();
    public void m3(){
        synchronized (object){}
    }
    

锁膨胀升级过程

锁的状态有四种,无锁状态、偏向锁、轻量级锁和重量级锁。
随着锁竞争的加剧,锁会升级,锁的升级过程不可逆:
偏向锁 -> 轻量级锁 -> 重量级锁

JVM对synchronized进行了很多优化,包括偏向锁、轻量级锁、自旋锁、锁消除

偏向锁

在大多数情况下,锁不存在多线程竞争,并且总是由同一个线程多次获得,所以为了减少同一线程获取锁的开销引入了偏向锁。
当线程获取到锁,锁就进入偏向锁模式,对象头Mark Word锁标识更改为偏向锁(01),当这个线程再次请求锁时,可以直接获取到该锁,不要任何同步操作。

偏向锁优化了锁的获取过程,提高了程序的性能。
对于无锁竞争的场合,偏向锁优化效果不错。
但锁竞争比较激烈时,申请锁的线程可能每次都不相同,偏向锁就失效了。
这时候偏向锁会升级为轻量级锁。

轻量级锁

偏向锁升级为轻量级锁,对象头锁标识变为轻量级锁标志(00)。
轻量级锁提升性能的依据是:对对大部分的锁,在整个同步周期内都不存在竞争。意思就是说虽然有多个线程访问同一个锁,但这些线程都是交替执行同步块,不会存在同一时间去访问同一个锁的场景,这种场景一旦出现,就会导致轻量级锁膨胀为重量级锁。

自旋锁

在轻量级锁失败后,JVM还会进行自旋锁的优化。依据是,在大多数情况下,线程持有锁的时间不会太长,这时候让申请锁的线程进行一定数量的空循环(自旋)等待,自旋完之后如果持有锁的线程已经释放了锁,那此时就可以获取锁了。
但如果还不能获取锁,锁就会升级为重量级锁,将线程在操作系统层面挂起。

线程的挂起是重操作,需要从用户态切换到内核态,这个状态转换需要相对比较长的时间。
自旋锁的优化依据:线程挂起消耗的时间 > 线程持有锁的时间

锁消除

通过逃逸分析可以得知方法内的一个变量是否会被方法外部访问,如果不会,JVM会将该变量的锁消除掉。
如下StringBuffer#append虽然是同步方法,但StringBuffer是一个局部变量,没有被方法外部所使用,不会多线程竞争这个StringBuffer,JVM会将这个方法中的StringBuffer#append的锁消除掉。

public String add(String str){
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(str);
        return stringBuffer.toString();
    }

逃逸分析需要JVM运行在server模式(默认),同时开启逃逸分析(默认开启)
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+EliminateLocks 表示开启锁消除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cuidianjay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值