synchronized的实现原理

作用

1. 原子性,保证代码块同一时间只被一个线程访问到;

2. 可见性,在进入同步代码块后,保证被锁变量取到的值就是真实值;(利用了java的内存模型特性:对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值

3. 有序性,有效解决代码重排问题,即unlock和lock操作倒置执行的问题;(实际上,synchronize是无法禁止指令重排和处理器优化的,实际上还是有重排的,重排后只是不影响当前线程,但有可能会影响其他线程

原理

synchronized的数据同步实际上是依赖锁来实现,包含三种形式:

  • 修饰静态方法,锁对象是Class实例,此时是一个全局锁;
  • 修饰实例方法,锁对象是对象实例(this);
  • 修饰代码块,锁对象是指定的某个对象;

从语法上讲,synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中,锁有个专门的名字:对象监视器(Object Monitor);

注意,synchronized内置锁(监视器锁)是一种对象锁(锁的是对象而非引用变量),作用粒度是对象 ,可以用来实现对临界资源的同步互斥访问 ,是可重入的(一个所可以被同一个对象获取多次,但同时也需要释放多次)。其可重入最大的作用是避免死锁,如:

子类同步方法调用了父类同步方法,如没有可重入的特性,则会发生死锁;

同步方法的递归调用;

同一时间,锁只能有一个持有人,且只能由持有人自行释放,其他人要想获取一个被持有着的锁只能等待,即线程会进入阻塞状态;

  • 修饰方法时,该方法的常量池中会被添加一个名为ACC_SYNCHRONIZED标志。调用方法时,如果检测到有ACC_SYNCHRONIZED,就需要先获取监视器锁才能继续,方法执行完之后再释放监视器锁。如果在方法执行过程中发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前锁会被自动释放。
  • 修饰代码块时,通过使用monitorentermonitorexit两个指令实现。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁;
    monitorenter指令:插入在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该对象监视器的所有权,即尝试获得该对象的锁;
    monitorexit指令:插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit;

注意,两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

监视器(Monitor)

任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。 

那什么是Monitor?可以把它理解为 一个同步工具,也可以描述为 一种同步机制,它通常被 描述为一个对象。
与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。也就是通常说synchronized的对象锁,MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时:

  1. 首先会进入 _EntryList 集合,当线程获取到对象的monitor后,进入 _Owner区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1;
  2. 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒;
  3. 若当前线程执行完毕,也将释放monitor(锁)并复位count的值,以便其他线程进入获取monitor(锁);

原文:https://www.cnblogs.com/aspirant/p/11470858.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值