synchronize原理

本文转自:https://blog.csdn.net/u011212394/article/details/82228321

一.synchronized的三种应用方式

1.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

2.修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象

二.synchronized的字节码指令

       synchronized同步块使用了monitorentermonitorexit指令实现同步,这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。

       线程执行到monitorenter执行时,会尝试获取对象所对应的的monitor所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的所有权。

三. synchronized的锁的原理

        两个重要的概念:一个是对象头,另一个是monitor

Java对象头

       在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头(Mark Word、Class Metadata Address)、实例数据和对齐填充;Java对象头是实现synchronized的锁对象的基础。一般而言,synchronized使用的锁对象是存储在Javad对象头里。它是轻量级锁和偏向锁的关键。

  • Mark Word

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。Java对象头一般有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit)。

  • Class Metadata Address

类型指针,既是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • Array length

如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。

Monitor

        Monitor是一个同步工具,它内置于每一个Object对象中,相当于一个许可证。拿到许可证即可以进行操作,没有拿到则需要阻塞等待。

        在Hotspot虚拟机中,通过ObjectMonitor类来实现monitor。

synchronized锁的优化

jdk1.6以后对synchronized的锁进行了优化,引入了偏向锁、轻量级锁,锁的级别从低到高逐步升级:无锁- >偏向锁- >轻量级锁- >重量级锁

自旋锁与自适应自旋

        线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获取了锁,所以他们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大地提升了性能。

        而适应性自旋,是赋予了自旋一种学习能力,它并不是固定自旋10次以下。它可以根据它前面线程的自旋情况。从而调整它的自旋,甚至是不经过自旋而直接挂起。

锁消除

        对不会存在线程安全的锁进行消除。

锁粗化

        如果JVM检测到有一串零碎的操作都对同一个对象加锁,将会把锁粗化到整个操作外部,如循环体。

偏向锁

       多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让其获得锁的代价更低引入了偏向锁。

       当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。

        如果测试失败,则需要在测试一下Mark Word中偏向锁的标识是否设置成01(表示当前是偏向锁)。

        如果没有设置,则使用CAS竞争锁。

        如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

轻量级锁

        引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS竞争锁,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

重量级锁

        重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于低层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态内核态的切换,切换成本非常高。

锁升级

        偏向锁升级轻量锁:当一个对象持有偏向锁,一旦第二个线程访问这个对象,如果产生竞争,偏向锁升级为轻量级锁。

        轻量级锁升级重量级锁:一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

四. wait和notify的原理

        调用wait方法,首先会获取监视器锁,获取成功以后,会让当前线程进入等待状态进入等待队列并且释放锁。

        当其他线程调用notify后,会选择从等待队列中唤醒任意一个线程,而执行完notify方法以后,并不会立马唤醒线程,原因是当前的线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完monitorexit指令以后,也就是锁释放以后,处于等待队列中的线程就可以开始竞争锁了。

五.wait和notify为什么需要在synchronized里面?

        wait方法的语义有两个,一个是释放当前的对象锁,另一个是使得当前线程进入阻塞队列,而这些操作都和监视器是相关的,所以wait必须要获得一个监视器锁。

        而对于notify来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里,所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

 

synchronize 关键字是用于实现线程同步的一种机制,其原理是通过在代码块或方法上加锁,来确保多个线程在执行该代码段时的互斥性。其实现原理如下: 1. 锁对象:synchronize 使用的锁对象可以是任意对象。当一个线程进入 synchronized 代码块时,它会尝试获取锁;如果锁没有被其他线程占用,则该线程获取锁,否则该线程将进入阻塞状态,直到锁被释放。 2. 对象监视器:在 Java 对象的内部数据结构中,每个对象都有一个关联的对象监视器。对象监视器负责管理锁的获取和释放,以及线程在对象上的等待和唤醒操作。当一个线程成功获取锁时,对象监视器会记录该线程的标识,并将对象的锁计数器加一。同时,其他线程在尝试获取锁时,将进入对象的等待队列,等待锁的释放。 3. 原子性操作:synchronize 关键字使得一些复合操作具有原子性。即当一个线程执行 synchronized 代码块时,其他线程无法同时进入该代码块,保证了多个线程对共享数据的同步访问。这样可以避免出现并发访问共享资源导致的数据不一致性和线程安全问题。 总之,synchronize 实现线程同步的原理是通过加锁和对象监视器来实现的。当多个线程竞争同一个锁时,只有一个线程能够获得锁,并执行 synchronized 代码块,其他线程将进入阻塞状态,直到锁被释放。这样就确保了共享数据的安全访问和操作的原子性,避免了多线程并发执行导致的数据不一致性问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值