欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > Synchronized的底层实现

Synchronized的底层实现

2024/10/25 0:25:56 来源:https://blog.csdn.net/qq_62923382/article/details/142237302  浏览:    关键词:Synchronized的底层实现

Synchronized用法

Synchronized 是 Java 中的一个重要关键字,主要是用来加锁的。在使用Synchronized的时候需要指定一个对象,所以synchornized也被称为对象锁

synchronized 的使用方法比较简单,主要可以用来修饰方法和代码块。根据其锁定的对象不同,可以用来定义同步方法和同步代码块。总的来说有三种用法:

1. 作用在实例方法上

修饰实例方法,相当于对当前实例对象this加锁,this作为对象监视器

public class main{public synchronized void hello(){System.out.println("hello world");}
}

2. 作用在静态方法上

修饰静态方法,相当于对main类的Class对象加锁,main类的Class对象作为对象监视器。

public class main{public synchronized static void helloStatic(){System.out.println("hello world static");}
}

3. 作用在代码块上

指定加锁对象,对给定对象加锁,括号括起来的test对象就是对象监视器。

public class main{Object test = new Object();        synchronized (test){System.out.println("hello world");}
}

Synchronized的原理

先说结论:Synchronized的底层原理是通过Monitor对象来完成的

Monitor是什么?

Monitor 对象被称为管程或者监视器锁,是由jvm提供,c++语言实现。在Java中,每一个对象实例都会关联一个Monitor对象。这个Monitor对象既可以与对象一起创建销毁,也可以在线程试图获取对象锁时自动生成。当这个Monitor对象被线程持有后,它便处于锁定状态。

Monitor 大致结构如下图所示

Monitor内部具体的存储结构:

Owner:存储当前获取锁的线程,只能有一个线程可以获取

EntryList:存储没有抢到锁的线程,这些都是处于Blocked状态的线程

WaitSet:存储调用了wait方法的线程,这些都是处于Waiting状态的线程

Count:用来记录该对象被线程获取锁的次数,这也说明了synchronized是可重入的

其中在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,ObjectMonitor.hpp文件如下所示:

ObjectMonitor() {_header       = NULL;_count        = 0; // 用来记录该对象被线程获取锁的次数,这也说明了synchronized是可重入的_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;  // 指向持有ObjectMonitor对象的线程_WaitSet      = NULL;  // 处于wait状态的线程,会被加入到_WaitSet,调用了wait方法之后会进入这里_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ; // 没没有抢到锁,处于等待锁block状态的线程,会被加入到该列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;
}

同步代码块

我们通过借助javap命令查看synchronizedTest.clsss的字节码

public class synchronizedTest {static Object test=new Object();public static void main(String[] args) {synchronized (test){System.out.println("test synchronized");}}
}

找到这个类的class文件,在class文件目录下执行 javap -v synchronizedTest.class ,

反汇编效果如下:

可以看到,字节码是通过 monitorenter monitorexit 两个指令进行控制的。

monitorenter 是上锁开始的地方,monitorexit 是解锁的地方,其中被monitorenter和monitorexit包围住的指令就是上锁的代码 。

  1. monitorenter,当执行到monitorenter指令时,线程就会去尝试获取该对象对应的Monitor的所有权,即尝试获得该对象的锁。如果当前monitor的计数器为0时,线程就会获得monitor对象锁,并且计数器Count自增1,那么该线程就是monitor的拥有者(owner)。
  2. 如果该线程已经是monitor的拥有者,又重新进入,就会把计数器Count再次自增1。也就是可重入的。
  3. monitorexit,执行monitorexit的线程必须是monitor的拥有者,指令执行后,monitor的计数器Count减1,如果减1后计数器Count为0,则该线程会释放monitor对象锁。其他被阻塞的线程就可以尝试去获取monitor的所有权。
  4. 倘若其他线程已经拥有monitor 的所有权,那么当前线程获取monitor对象锁失败将被阻塞并进入到EntryList中,直到锁被释放为止。

monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出释放锁

详细流程:

  • 加锁时,即遇到Synchronized关键字时,线程会先进入monitor的_EntryList队列阻塞等待。
  • 如果monitor的_owner为空,则从队列中移出并赋值与_owner。
  • 如果在程序里调用了wait()方法,则该线程进入_WaitSet队列。我们都知道wait方法会释放monitor锁,即将_owner赋值为null并进入_WaitSet队列阻塞等待。这时其他在_EntryList中的线程就可以获取锁了。
  • 当程序里其他线程调用了notify/notifyAll方法时,就会唤醒_WaitSet中的某个线程,这个线程就会再次尝试获取monitor锁。如果成功,则就会成为monitor的owner。
  • 当程序里遇到Synchronized关键字的作用范围结束时,就会将monitor的owner设为null,退出。


 

同步方法的底层实现

我们通过借助javap命令查看SyncTest.clsss的字节码

public class SyncTest {public static void main(String[] args) {hello();}public static synchronized void hello(){System.out.println("hello synchronized!");}
}

找到这个类的class文件,在class文件目录下执行 javap -v SyncTest.class ,

反汇编效果如下:

可以看到,在字节码的体现上,这里并没有monitorenter和moniterexit两条指令,而是只给方法加了一个 flag:ACC_SYNCHRONIZED ,这其实也容易理解,因为整个方法都是同步代码,因此就不需要标记同步代码的入口和出口。当线程线程执行到这个方法时会判断是否有ACC_SYNCHRONIZED标志,如果有的话则会尝试获取monitor对象锁。当该对象的 monitor 的计数器count为0时,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。当同步方法执行完后,该对象的 monitor 的计数器减1,计数器的值为0时,执行线程将释放 monitor(锁),其他线程才有机会持有 monitor。

总的来说,synchronized的底层原理是通过monitor对象来完成的,对于同步代码块是使用monitorenter和monitorexit指令来完成加锁和解锁。对于同步方法则是通过判断方法上是否有ACC_SYNCHRONIZED标志来尝试获取monitor对象锁。

方法级的同步是隐式的(同步方法)。同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。当某个线程要访问某个方法的时候,会检查是否有 ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

同步代码块使用 monitorenter 和 monitorexit 两个指令实现。 可以把执行 monitorenter 指令理解为加锁,执行 monitorexit 理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,当一个线程获得锁(执行 monitorenter )后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行 monitorexit 指令)的时候,计数器再自减。当计数器为 0 的时候。锁将被释放,其他线程便可以获得锁。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com