美文网首页
Synchronized 底层工作原理

Synchronized 底层工作原理

作者: b335eb9201c3 | 来源:发表于2020-03-06 17:04 被阅读0次

    4.1.9.4. Synchronized 同步锁

    synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重

    入锁。

    Synchronized 作用范围

    1.  作用于方法时,锁住的是对象的实例(this);

    2.  当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen

    (jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,

    会锁所有调用该方法的线程;

    3.  synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,

    当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

    Synchronized 核心组件

    1) Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;

    2) Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;

    3) Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;

    4) OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;

    5) Owner:当前已经获取到所资源的线程被称为 Owner;

    6) !Owner:当前释放锁的线程。

    Synchronized 实现

    1. JVM 每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,

    ContentionList 会被大量的并发线程进行 CAS 访问,为了降低对尾部元素的竞争,JVM 会将

    一部分线程移动到 EntryList 中作为候选竞争线程。

    2.  Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定

    EntryList 中的某个线程为 OnDeck 线程(一般是最先进去的那个线程)。

    3.  Owner 线程并不直接把锁传递给 OnDeck 线程,而是把锁竞争的权利交给 OnDeck,

    OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在

    JVM 中,也把这种选择行为称之为“竞争切换”。

    4.  OnDeck 线程获取到锁资源后会变为 Owner 线程,而没有得到锁资源的仍然停留在 EntryList

    中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify

    或者 notifyAll 唤醒,会重新进去 EntryList 中。

    5.  处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统

    来完成的(Linux 内核下采用 pthread_mutex_lock 内核函数实现的)。

    6.  Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,等待的线程会先

    尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是

    不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁

    资源。

    参考:https://blog.csdn.net/zqz_zqz/article/details/70233767

    7.  每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加

    上 monitorenter 和 monitorexit 指令来实现的,方法加锁是通过一个标记位来判断的

    8.  synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线

    程加锁消耗的时间比有用操作消耗的时间更多。

    9.  Java1.6,synchronized 进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向

    锁等,效率有了本质上的提高。在之后推出的 Java1.7 与 1.8 中,均对该关键字的实现机理做

    了优化。引入了偏向锁和轻量级锁。都是在对象头中有标记位,不需要经过操作系统加锁。

    10. 锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀;

    11. JDK 1.6 中默认是开启偏向锁和轻量级锁,可以通过-XX:-UseBiasedLocking 来禁用偏向锁。

    4.1.9.5. ReentrantLock

    ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完

    成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等

    避免多线程死锁的方法。

    Lock 接口的主要方法

    1.  void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经

    被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.

    2.  boolean tryLock():如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false. 该方法和

    lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用,

    当前线程仍然继续往下执行代码. 而 lock()方法则是一定要获取到锁, 如果锁不可用, 就一

    直等待, 在未获得锁之前,当前线程并不继续向下执行.

    3.  void unlock():执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程

    并不持有锁, 却执行该方法, 可能导致异常的发生.

    4.  Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,

    当前线程只有获取了锁,才能调用该组件的 await()方法,而调用后,当前线程将缩放锁。

    5.  getHoldCount() :查询当前线程保持此锁的次数,也就是执行此线程执行lock方法的次

    数。

    6.  getQueueLength():返回正等待获取此锁的线程估计数,比如启动 10 个线程,1 个

    线程获得锁,此时返回的是 9

    7.  getWaitQueueLength:(Condition condition)返回等待与此锁相关的给定条件的线

    程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了

    condition 对象的 await 方法,那么此时执行此方法返回 10

    8.  hasWaiters(Condition condition):查询是否有线程等待与此锁有关的给定条件

    (condition),对于指定 contidion 对象,有多少线程执行了 condition.await 方法

    9.  hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁

    10. hasQueuedThreads():是否有线程等待此锁

    11. isFair():该锁是否公平锁

    12. isHeldByCurrentThread(): 当前线程是否保持锁锁定,线程的执行 lock 方法的前后分

    别是 false 和 true

    13. isLock():此锁是否有任意线程占用

    14. lockInterruptibly():如果当前线程未被中断,获取锁

    15. tryLock():尝试获得锁,仅在调用时锁未被线程占用,获得锁

    16. tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持,

    则获取该锁。

    非公平锁

    JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了

    是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非

    程序有特殊需要,否则最常用非公平锁的分配机制。

    121623125152125125

    公平锁

    公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,

    ReentrantLock 在构造函数中提供了是否公平锁的初始化方式来定义公平锁。

    ReentrantLock  与 synchronized

    1.  ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作,与 synchronized 会

    被 JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出

    现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操

    作。

    2.  ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁。这种情况下需要

    使用 ReentrantLock。

    ReentrantLock 实现

    public class MyService {

    private Lock lock = new ReentrantLock();

    //Lock lock=new ReentrantLock(true);//公平锁

    //Lock lock=new ReentrantLock(false);//非公平锁

    private Condition condition=lock.newCondition();//创建 Condition

    public void testMethod() {

    try {

    lock.lock();//lock 加锁

    //1:wait 方法等待:

    //System.out.println("开始 wait");

    condition.await();

    //通过创建 Condition 对象来使线程 wait,必须先执行 lock.lock 方法获得锁

    //:2:signal 方法唤醒

    condition.signal();//condition 对象的 signal 方法可以唤醒 wait 线程

    for (int i = 0; i < 5; i++) {

    System.out.println("ThreadName=" + Thread.currentThread().getName()+ (" " + (i + 1)));

    }

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    finally

    121623125152125125

    {

    lock.unlock();

    }

    }

    }

    Condition 类和 Object 类锁方法区别区别

    1.  Condition 类的 awiat 方法和 Object 类的 wait 方法等效

    2.  Condition 类的 signal 方法和 Object 类的 notify 方法等效

    3.  Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效

    4.  ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的

    tryLock 和 lock 和 lockInterruptibly 的区别

    1.  tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit

    unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false

    2.  lock 能获得锁就返回 true,不能的话一直等待获得锁

    3.  lock 和 lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,

    lock 不会抛出异常,而 lockInterruptibly 会抛出异常。

    相关文章

      网友评论

          本文标题:Synchronized 底层工作原理

          本文链接:https://www.haomeiwen.com/subject/warwrhtx.html