synchronized的底层实现原理(偏向锁,轻量锁,重量锁)

synchronized实现原理

面试的时候,面试官问我synchronzied,尴尬没有看过,不会,哈哈哈,便恶补synchronzied,顺便还有线程池,在我的另外一篇博客里面。

基本上写的博客比较少,很多东西都写的不是很规范,不过也算是自己的一个学习笔记和总结吧。

刚开始接触多线程的时候,用到的就是synchronzied操作,直到现在synchronzied在项目中仍然有使用到,他可以保证线程的互斥,也叫同步锁,重锁,能够有效的解决线程竞争资源的情况。在一个线程获得同步锁之后,其他线程处于等待阻塞状态,必须等这个线程执行完毕,其他线程才能继续执行,但这也就导致了效率比较低下,但是在jdk1.6之后,synchronzied做了大量的优化,包括引入偏向锁,轻量级锁,它是根据线程的数量来进行锁的优化升级。现在让我们看看synchronzied到底是怎么运行的吧。

1.synchronzied的特点

   1)原子性  ,不可分割,要么全部执行成功,要么全部失败

   2)可见性,一个线程对变量的修改,其他线程能够看见

   3)有序性,代码执行的逻辑是按照编写时的顺序执行的

2.锁升级

   

对对象进行锁的时候,先来了解一下对象的内部结构吧,

在锁进行升级的时候,对象的标记位会进行变化

64位虚拟机如下图所示,其中两位代表对象锁的状态

 

下面以一段代码++进行示例说明,这段代码是不具有原子性的,所以存在并发安全问题,最后得到的值会小于50000,也就是第一个线程改变了它的值后,还没有将新值刷新到内存中,第二个线程拿到的还是原来的旧值

int count=0;

main(){
   for(int i=0;i< 50000;i++){
      sum();
   }
  System.out.println(count);
}


void sum(){
   count++;
}

  所以这里可以在sum方法里面使用synchronized修饰count对象。即每条线程到sum方法的时候,只有一条线程能够拿到count对象,其他线程被阻塞,当这条线程执行完毕,数据被更新后,其他线程才能进入。但是这对于整个程序来说,严重的影响了性能,导致响应很慢,虽然保证了运算结果的准确性。所以在jdk1.6开始后,jvm开始对它进行了一系列的优化,在此基础上,引入了偏向锁,轻量级锁,重量级锁。重量级锁就是线程阻塞操作。

void sum(){

  synchronized(this){
    count++;
  }
}

 

1)偏向锁

      是指只有一个线程的时候,对象的对象头会保存当前线程的id,而其中对象头中的标记位由001变为101状态。

2)轻量级锁

      当线程出现抢占资源的时候,会由偏向锁升级为轻量级锁,常见的轻量级锁有cas,又称自旋锁,乐观锁,此时对象头中的标记位变为00.

      cas,compare and swap即比较和替换。

       如AtomicInteger 是一个cas自旋锁,刚才的++操作可以用如下代码来执行。

while(true){
   int oldValue = atomic.get();
   int newValue = oldValue + 1;
   if(atomic.compareAndSet(oldValue,newValue)){
      break;
    }

}

   上面一段代码是不存在线程阻塞的,可以同时对值进行操作,atomic的底层value值是voletile关键字来修饰的,所以当有线程对oldvalue+1操作后,其他线程能够立马看见,然后再执行compareAndSet操作的时候,这是一个原子操作,会进行比较和内存中的oldvalue,如果被其他线程改变了,那么不会对将新值改变到内存中。所以这个唯一的优点就是可以进行并发操作,不存在阻塞线程等待的问题。但是凡事都有利有弊,只有更好,没有最好。它的缺点也是显而易见的,在并发操作的时候,很多线程都是在进行比较,一个线程有可能循环好几次才能从内存中获取到对应的值,这就是所谓的空转现象,此时占用cpu资源也是非常的高。所以在达到一定的并发时,会自动升级为重量级锁。

 

3)重量级锁     

     重量级锁就是最开始的线程阻塞操作,又叫悲观锁,此时对象头中的标记位变为10.它锁住的是一整段代码,当整段代码执行完后,其他线程才能获得锁,继续执行。


3.synchronized 实现原理

    synchronized 可以修饰方法,代码块;

    在修饰代码块的时候,由于是关键字,直接由jvm进行管理,我们看不见它的源码,通过代码的反编译,我们能看到有一个monitorenter指令和monitorexit指令,当执行代码块的时候,对象拥有monitor这个队形锁,代码执行完后,会释放monitor对象锁,也就是执行monitorexit指令。我们会发现第19行还有一个monitorexit指令,这个是发生异常后,也会释放对象锁。

在修饰方法的时候,我们看到flags会有一个ACC_SYNCHRONIZED标记,这个其实也是隐式的获取monitor对象锁。

然后我们来看一下这个monitor到底是个什么东西,底层的源码是由c++来实现的

   monitor对象主要由以下几个字段来组成。

   -count  记录个数

  - wanitset   处于wait状态,会被加入到 waitset

 -entryList    处于等待锁状态的线程,会被加入到entryList中。

当monitor对象被线程持有时,count会进行加1,当线程释放monitor对象时,count会减1操作,用count来表示monitor对象是否被持有。

而且synchronized具有可重入性,也就是当一个线程重复持有锁时,count会一直加,释放的时候,会一直减,直到为0时,才算这块执行完。

 

4.总结

   synchronized总的来说,在jdk1.6以前非常的笨重,耗资源,性能低。但是被优化之后,引入的偏向锁,轻量级锁等,使性能进一步得到了提高。

现在很低底层的源码都用到了synchronized来保证代码的原子性,可见性有序性等。

 

 

 

    

 

 

    

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值