synchronized关键字详解

使用了synchronized修饰的同步代码不再像1.6以前那样是重量级锁了,1.6之后实现了优化,优化的策略就是锁升级。分为了偏向锁、轻量级锁和重量级锁,在了解这些锁之前先要了解下锁对象的概念。

锁对象

synchronized锁有二种表示形式:实例对象,类对象。当线程走到synchronized修饰的代码时,首先会去对应的锁对象中去拿到相应的锁,然后进行执行代码。
那么对象中是如何存储锁的呢?

对象在jvm中的存储形式包括:对象头,实例数据,填充数据。其中对象头中存储了各种锁的标识,对象头的表现形式如下图
在这里插入图片描述
其中偏向锁和轻量级锁其实是无锁的,偏向锁是通过cas操作实现轻量级锁通过自旋形式获得锁。重量级锁才是正真的加了锁。

偏向锁

只有线程A访问:
当只有线程A去访问同步代码块时,它会在对象头中存储线程的id和偏向锁的标志1,当线程A下一次再次访问时,直接比较线程A的id和锁对象中存储的线程id是否相同。
但是正常业务中如果使用了同步代码,那么基本上都是会存在多个线程进行访问的,所以可以选择关闭偏向锁来减少一次无意义的锁升级从而提升一点性能。

开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0(默认是开启的)
关闭偏向锁:-XX:-UseBiasedLocking

轻量级锁

线程A和线程B交替访问:
接着偏向锁续说,当线程A已经拿到偏向锁之后,此时有另一个线程B也来访问同步代码块时,此时线程B的线程id和对象头中存储的id是不一样的,导致获取偏向锁失败,此时偏向锁撤销,锁会升级轻量级锁。

那么轻量级的锁是如何获得的呢?

当线程获取轻量级锁时,如果获取不到那么就会通过自旋来不断的获取锁资源,代码逻辑如下:

for(;;){
   if(cas){
       //获得锁成功
       return;
   }
   continue;
}

当然我们需要指定自旋的次数,它只是在一定的次数以内它的性能才会大于阻塞的方式,如果在指定的次数之内还没有获得锁,那么就会升级到重量级锁。

设置自旋次数:-XX:PreBlockSpin=10 jdk默认时10
自适应自旋:自适应意味着自旋的时间不再是固定的, 而是由前一次在同一个锁上的自旋时间以及锁拥有者的状态来决定。如果在同一个锁对象上, 自旋等待刚好成功获得锁, 并且在持有锁的线程在运行中, 那么虚拟机就会认为这次自旋也是很有可能获得锁, 进而它将允许自旋等待相对更长的时间。如果对于获得锁使用自旋的方式很少成功的话,那么虚拟机会把自旋的次数或者时间设置的非常短甚至省略,从另一个角度来说也是一种性能的提升。

偏向锁的获取与撤销流程图

在这里插入图片描述

重量级锁

没有获得锁的线程会直接被阻塞(CPU挂起此线程)
重量级锁是使用对象的监视器实现的,为什么每个对象都可以成为锁对象,是因为每个对象都有一个监视器,并且监视器是互斥的,监视器的实现是操作系统提供的,不同的操作系统实现方式不一样。(linux)线程的挂起实际是把线程从用户态转换成内核状态。

public void method(){
        synchronized(this){

        }
}
对应的字节码如下
 public void method();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_1
         5: monitorexit
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
      Exception table:
         from    to  target type
             4     6     9   any
             9    12     9   any
      LineNumberTable:
        line 6: 0
        line 8: 4
        line 9: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/huawei/Demo;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class com/huawei/Demo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

其中3和5是成对出现的,11是异常时释放锁。
monitorenter:根据锁的等级不同获取锁的方式不一样,重量级锁如下:

monitorenter-- -去获取对象的监视器---->monitor(对象的监视器)----获取成功------>拿到锁对象-----monitorexit----->释放锁

对象头中mointor的实现(markOop.hpp)

ObjectMonitor* monitor() const {

    assert(has_monitor(), "check");

    // Use xor instead of &~ to provide one extra tag-bit check.

    return (ObjectMonitor*) (value() ^ monitor_value);

  }

当其他线程来获取锁对象时,获取不到会被阻塞(被阻塞的线程它的状态是处于block状态,那么什么时候线程处于block状态就很明了了),这里可以简单的理解为被阻塞的线程被放到了一个同步队列中,当持有锁对象的线程执行monitor指定之后会把队列中的线程唤醒(随机唤醒一个),然后所有线程再去争抢锁。

wait、notify和notifyAll

这三个方法实际上是线程的一种通信机制
wait:实现线程阻塞、释放锁和cpu资源(sleep不会释放锁,会释放cpu资源,会是的线程变成waiting状态)
notify:把等待队列中一个线程放到同步队列中(如果有多个线程使用同一把锁阻塞,唤醒哪一个由jvm算法决定)
notifyAll:把等待队列中所有线程放到同步队列中

调用wait方法时,线程会被放到等待队列中,再调用notify方法时,会把等待队列的一个线程放到同步队列中去(同步队列才有资格去争抢锁,等待队列没有资格争抢锁),notifyAll会把等待队列中的所有元素都放到同步队列中,当锁对象被释放时同步队列开始抢占锁资源。

wait虽然是任何类都可以调用,但是调用wait方法必须得到调用对象的monitor,那如何得到对象的监视器呢,通过synchronized关键字或者Lock的实现类。如果没有拿到此对象的监视器就调用wait方法,则会报IllegalMonitorStateException的错误。打个比方就像人人都有一部电信手机,他们之间可以打电话联系,但如果没有电信的信号塔,那么你的手机就不能打通电话了。

例子:

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println("start ThreadB");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end ThreadB");
        }
    }
}
public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println("start ThreadA");
            lock.notify();
            System.out.println("end ThreadA");
        }
    }
}
public static void main(String[] args) {
        Object lock = new Object();
        new ThreadB(lock).start();
        new ThreadA(lock).start();
    }

结果

start ThreadB
start ThreadA
end ThreadA
end ThreadB
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值