Synchronized详解

Synchronized简介

基本上所有的并发模式在解决线程冲突问题时,都是采用序列化访问共享资源的方案,即在给定时刻只允许一个任务访问共享资源,通常是在代码前面加上一条锁语句来实现,表示在某一时间段内只有一个任务可以运行这段代码,锁语句产生的互斥效果的机制被称为互斥量synchronized用于修饰方法和代码块。

JDK5.0以前解决线程安全问题的方式:

//同步代码块
synchronized(同步监视器){
    //需要被同步的代码
}

说明:

  1. 操作共享数据的代码即为需要被同步的代码

  2. 共享数据,多个线程共同操作的变量,比如ticket就是共享数据

  3. 同步监视器:俗称”锁“,任何一个类的对象都可以充当锁 要求:多个线程必须共用同一把锁

使用synchronized关键字的注意事项:

- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待

- 每个实例都对应有自己的一把锁(`this`),不同实例之间互不影响

  - 如果锁对象是`*.class`以及`synchronized`修饰的`static`方法时,所有对象共用一把锁

- `synchronized`修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁

synchronized的使用方式

synchronized分为对象锁、普通方法锁以及类锁;

对象锁

手动指定锁对象,也可以是this,也可以是自定义对象锁

public class SynchronizedObjectLock implements Runable{
  static SynchronizedObjectLock instance = new SynchronizedObjectLock();
  
  @Override
  public void run(){
    synchronized(this){
      System.out.println("我是线程:"+Thread.currentThread().getName());
      try{
        Thread.sleep(3000);
      }catch(InterruptedException e){
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+"结束");
    }
  }
  public static void main(String[] args){
    Thread t1=new Thread(instance);
    Thread t2=new Thread(instance);
    t1.start();
    t2.start();
  }
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runable{
  static SynchronizedObjectLock instance = new SynchronizedObjectLock();
  Object block1 = new Object();
  Object block2 = new Object();
  
  @Override
  public void run(){
    synchronized(block1){
      System.out.println("block1锁,我是线程"+Thread.currentThread().getName());
      try{
        Thread.sleep(3000);
      }catch(InterruptedException){
        e.printStackTrace();
      }
      System.out.println("block1锁,"+Thread.currentThread().getName()+"结束");
    }
    synchronized(block2){
      System.out.println("block2锁,我是线程"+Thread.currentThread().getName());
      try{
        Thread.sleep(3000);
      }catch(InterruptedException){
        e.printStackTrace();
      }
      System.out.println("block2锁,"+Thread.currentThread().getName()+"结束");
    }
  }
  public static void main(String[] args){
    Thread t1=new Thread(instance);
    Thread t2=new Thread(instance);
    t1.start();
    t2.start();
  }
}
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0  // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束

普通方法锁

如果操作共享数据的代码完整的声明在一个方法中,我们就可以将此方法声明为一个同步方法,锁对象默认是this

public class SynchronizedObjectLock implements Runnable{
  static SynchronizedObjectLock instance = new SynchronizedObjectLock();
  @Override
  public void run(){
    method();
  }
  
  public synchronized void method(){
    System.out.println("我是线程"+Thread.currentThread().getName());
    try{
      Thread.sleep(3000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"结束");
  }
  
  public static void main(String[] args){
    Thread t1=new Thread(instance);
    Thread t2=new Thread(instance);
    t1.start();
    t2.start();
  }
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

如果同一个类的多个方法上都添加了synchronized,那么这几个方法在同步执行时也是互斥的(抢占锁)


类锁

上述同步代码块和同步方法都是采用的对象锁,因此只能在并发调用同一个对象的方法时才会互斥,如果是多个对象分开调用,并不会产生互斥,类锁可以解决这一问题。
所谓类锁是指synchronized修饰静态方法指定锁对象为Class对象

public class SynchronizedObjectLock implements Runnable{
  static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
  static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
  
  @Override
  public void run(){
    method();
  }
  //synchronized修饰static方法,默认锁为当前所在Class类,无论什么线程访问都只有这一把锁
  public static synchronized void method(){
    System.out.println("我是线程"+Thread.currentThread().getName());
    try{
      Thread.sleep(3000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"结束");
  }
  
  public static void main(String[] args){
    Thread t1 = new Thread(instance1);
    Thread t2 = new Thread(instance2);
    t1.start();
    t2.start();
  }
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runnable{
  static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
  static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
  
  @Override
  public void run(){
    synchronized(SynchronizedObjectLock.class){
      System.out.println("我是线程"+Thread.currentThread().getName());
      try{
        Thread.sleep(3000);
      }catch(InterruptedException e){
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+"结束");
    }
  }
  
  public static void main(String[] args){
    Thread t1 = new Thread(instance1);
    Thread t2 = new Thread(instance2);
    t1.start();
    t2.start();
  }
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

Synchronized原理分析

synchronized的实现机制就是对对象的加锁,Java中每个对象都隐含关联一个监视器ObjectMonitor,监视器通过c++实现内置在JVM中,监视器地址记录在对象的MarkWord上,synchronized通过ObjectMonitor实现对象的锁操作

JVM在内存中将对象划分为三部分:对象头、实例数据和对齐填充
在这里插入图片描述

当线程访问同步块时,首先需要获得锁并把相关信息存储在对象头中,这也是为什么waitnotifynotifyAll这些方法设计在Object类中的原因

Hostspot有两种对象头:

  • 数组类型:使用arrayOopDesc来描述对象头

  • 其他:使用instanceOopDesc来描述对象头

对象头由两部分组成:

  • MarkWord:存储自身的运行时数据,例如HashCodeGC年龄、锁相关信息等内容

  • Klass Pointer:类型指针指向它的类元数据的指针

对象头分为MarkWord和类型指针两部分,其中MarkWord用于存储对象自身的运行数据,如哈希值、GC分代年龄等,这部分占用32位或64位(按操作系统位数决定)。

以下是32位的空间布局,MarkWord会根据对象状态复用存储空间,例如对象未锁定状态下,采用25bit(HashCode)+4bit(GC年龄)+1bit(固定0)+2bit(锁标志位),当标志位=10表示对象处于重量级锁定时,剩余空间就用于存储ObjectMonitor对象的地址
在这里插入图片描述

以下是64位的Mark Word结构

  • 无锁状态时,前25bit不使用,31bit用于存储hashCode

  • 偏向锁状态时,前54bit用于存储线程的id,后2bit用于存储元数据

  • 轻量级锁状态时,前62bit都用于存储指向线程栈中Lock Record的指针

  • 重量级锁状态时,前62位用于存储指向互斥量的指针

64位MarkWord数据结构
在这里插入图片描述

重量级锁的底层原理

在重量级锁中,没有竞争到锁的对象会park被挂起,退出同步块时unpark唤醒后续线程,唤醒操作涉及到操作系统调度,会有额外的开销

ObjectMonitor中包含一个同步队列(由_cxq_EntryList组成)和一个等待队列(_WaitSet)

  • notifynotifyAll唤醒时,根据policy策略选择加入的队列(policy默认为0)

  • 退出同步块时,根据QMode策略来唤醒下一个线程(QMode默认为0)

并发的两大核心问题:互斥和同步都是可以通过管程来解决的

什么是管程?

管程是一把解决并发问题的万能钥匙,synchronized关键字以及waitnotifynotifyAll三个方法都是管程的组成部分

synchronizedmonitor锁机制和JDK并发包中的AQS很相似,只不过AQS中是一个同步队列、多个等待队列

源码解析

队列的协作流程图
在这里插入图片描述

在HotSpot中monitor是由ObjectMonitor实现的,其源码是用C++来实现的,源文件是ObjectMonitor.hpp

ObjectMonitor() {
  _header       = NULL;
  _count        = 0;
  _waiters      = 0,       // 等待中的线程数
  _recursions   = 0;       // 线程重入次数
  _object       = NULL;    // 存储该 monitor 的对象
  _owner        = NULL;    // 指向拥有该 monitor 的线程
  _WaitSet      = NULL;    // 等待线程 双向循环链表_WaitSet 指向第一个节点
  _WaitSetLock  = 0 ;
  _Responsible  = NULL ;
  _succ         = NULL ;
  _cxq          = NULL ;   // 多线程竞争锁时的单向链表
  FreeNext      = NULL ;
  _EntryList    = NULL ;   // _owner 从该双向循环链表中唤醒线程,
  _SpinFreq     = 0 ;
  _SpinClock    = 0 ;
  OwnerIsThread = 0 ;
  _previous_owner_tid = 0; // 前一个拥有此监视器的线程 ID
}

代码解析

  1. _owner:初始时为NULL,当有线程占有该monitor时owner标记该线程的ID。当线程释放monitor时owner恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线程安全

  2. _cxq:竞争队列所有请求锁的线程首先会被放入这个队列(单向队列)。_cxq是一个临界资源,JVM通过CAS原子指令来修改_cxq队列

这里需要注意:每当有新的节点入队,它的next指针总是指向之前队列的头节点,而_cxq指针会指向该新入队的节点,所以是后来居上
  1. _EntryList:_cxq队列中有资格成为候选资源的线程会被移动到该队列中

  2. _WaitSet:等待队列因为调用wait方法而被阻塞的线程会被放入该队列中

  3. _recursions:计数器,用于计算一个线程进入同一个monitor的次数,同一个线程多次进入同一个monitor称为重入,重入锁的基本概念参考:

[可重入原理:加锁计数器](https://flowus.cn/24b26c9d-028d-4790-b716-892218472289)
加锁和释放锁的原理
/**
 * @author caihuaxin
 * @version 1.0.0
 * @doc Synchronized演示
 * @date 2023-08-17 11:05:06
 */
public class SynchronizedDemo {
    Object object = new Object();
    public void method(){
        synchronized (object) {

        }
        method2();
    }
    private static void method2(){

    }
}
# 编译java为字节码文件
javac SynchronizedDemo.java
# 查看字节码文件反编译后的流程
javap -verbose SynchronizedDemo.class
F:\Project\Study\Java8NewFeature\src\com\carl\test\synchronizedTest>javap -verbose SynchronizedDemo.class
Classfile /F:/Project/Study/Java8NewFeature/src/com/carl/test/synchronizedTest/SynchronizedDemo.class
  Last modified 2023年10月23日; size 523 bytes
  MD5 checksum 7492a9fa5c8fe48f7311971e46f286a0
  Compiled from "SynchronizedDemo.java"
public class com.carl.test.synchronizedTest.SynchronizedDemo
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // com/carl/test/synchronizedTest/SynchronizedDemo
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
   #1 = Methodref          #2.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // java/lang/Object
   #3 = Fieldref           #5.#20         // com/carl/test/synchronizedTest/SynchronizedDemo.object:Ljava/lang/Object;
   #4 = Methodref          #5.#21         // com/carl/test/synchronizedTest/SynchronizedDemo.method2:()V
   #5 = Class              #22            // com/carl/test/synchronizedTest/SynchronizedDemo
   #6 = Utf8               object
   #7 = Utf8               Ljava/lang/Object;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               method
  #13 = Utf8               StackMapTable
  #14 = Class              #23            // java/lang/Throwable
  #15 = Utf8               method2
  #16 = Utf8               SourceFile
  #17 = Utf8               SynchronizedDemo.java
  #18 = NameAndType        #8:#9          // "<init>":()V
  #19 = Utf8               java/lang/Object
  #20 = NameAndType        #6:#7          // object:Ljava/lang/Object;
  #21 = NameAndType        #15:#9         // method2:()V
  #22 = Utf8               com/carl/test/synchronizedTest/SynchronizedDemo
  #23 = Utf8               java/lang/Throwable
{
  java.lang.Object object;
    descriptor: Ljava/lang/Object;
    flags: (0x0000)

  public com.carl.test.synchronizedTest.SynchronizedDemo();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field object:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 9: 0
        line 10: 4

  public void method();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field object:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: aload_1
         8: monitorexit
         9: goto          17
        12: astore_2
        13: aload_1
        14: monitorexit
        15: aload_2
        16: athrow
        17: invokestatic  #4                  // Method method2:()V
        20: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:
        line 12: 0
        line 14: 7
        line 15: 17
        line 16: 20
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class com/carl/test/synchronizedTest/SynchronizedDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "SynchronizedDemo.java"

在这里插入图片描述

monitor:监视器

monitorenter:尝试获取monitor的所有权,如果monitor计数器为0,则获取到monitor的所有权,并把锁计数器+1;如果计数器值大于0,其他线程再想获取就需要等待;如果这个monitor拿到了锁的所有权,又重入了这把锁,那这个锁计数器就会累加,计数器的值=同一个monitor进入锁的次数

monitorexit:释放monitor的所有权,monitor的锁计数器-1,如果-1后,计数器值不为0,则代表刚才是重入进来的,当前现成还继续持有这把锁的所有权,直到monitor的计数器为0,才真正释放锁
在这里插入图片描述

源码分析上述代码的执行流程

monitor竞争

  1. 通过CAS尝试把monitor的owner字段设置为当前线程ID

  2. 如果设置之前的owner指向当前线程,则说明当前线程是重入monitor,重入锁则会执行recursions++,记录重入的次数

  3. 如果当前线程是第一次进入该monitor,则设置rescursions为1,_owner为当前线程,该线程成功获取锁并返回

  4. 如果获取锁失败,则等待锁的释放

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  #ifdef ASSERT
    thread->last_frame().interpreter_frame_verify_monitor(elem);
  #endif
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),"must be NULL or an object");
  // 是否使用偏向锁  JVM 启动时设置的偏向锁-XX:-UseBiasedLocking=false/true
  if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {
      // 轻量级锁
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
  assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
         "must be NULL or an object");
  #ifdef ASSERT
    thread->last_frame().interpreter_frame_verify_monitor(elem);
  #endif
IRT_END

slow_enter方法主要是轻量级锁的操作,如果操作失败会膨胀为重量级锁,enter方法则为重量级锁的入口,源码如下:

void ATTR ObjectMonitor::enter(TRAPS) {
  Thread * const Self = THREAD ;
  void * cur ;
  // 省略部分代码
  
  // 通过 CAS 操作尝试把 monitor 的_owner 字段设置为当前线程
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
     assert (_recursions == 0   , "invariant") ;
     assert (_owner      == Self, "invariant") ;
     return ;
  }

 // 如果当前线程重入该monitor,则recursions++
  if (cur == Self) {
     _recursions ++ ;
     return ;
  }

    // 如果当前线程是第一次进入该 monitor, 设置_recursions 为 1,_owner 为当前线程
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    _recursions = 1 ;
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

    for (;;) {
      jt->set_suspend_equivalent();
        // 如果获取锁失败,则等待锁的释放;
      EnterI (THREAD) ;

      if (!ExitSuspendEquivalent(jt)) break ;
          _recursions = 0 ;
      _succ = NULL ;
      exit (false, Self) ;

      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);
  }
}

monitor等待

  1. 当线程进入等待状态,即被封装为ObjectWaiter对象node,状态设置为ObjectWaiter::TS_CXQ

  2. for循环通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中

  3. node节点push到_cxq列表之后,通过自旋锁尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起等待被唤起

  4. 当该线程被唤醒时,会从挂起点继续执行,通过ObjectMonitor::TryLock尝试获取锁

// 省略部分代码
void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    assert (Self->is_Java_thread(), "invariant") ;
    assert (((JavaThread *) Self)->thread_state() == _thread_blocked   , "invariant") ;

    // Try lock 尝试获取锁
    if (TryLock (Self) > 0) {
        assert (_succ != Self              , "invariant") ;
        assert (_owner == Self             , "invariant") ;
        assert (_Responsible != Self       , "invariant") ;
        // 如果获取成功则退出,避免 park unpark 系统调度的开销
        return ;
    }

    // 自旋获取锁
    if (TrySpin(Self) > 0) {
        assert (_owner == Self, "invariant");
        assert (_succ != Self, "invariant");
        assert (_Responsible != Self, "invariant");
        return;
    }

    // 当前线程被封装成 ObjectWaiter 对象 node, 状态设置成 ObjectWaiter::TS_CXQ
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;

    // 通过 CAS 把 node 节点 push 到_cxq 列表中
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // 再次 tryLock
        if (TryLock (Self) > 0) {
            assert (_succ != Self         , "invariant") ;
            assert (_owner == Self        , "invariant") ;
            assert (_Responsible != Self  , "invariant") ;
            return ;
        }
    }

    for (;;) {
        // 本段代码的主要思想和 AQS 中相似可以类比来看
        // 再次尝试
        if (TryLock (Self) > 0) break ;
        assert (_owner != Self, "invariant") ;

        if ((SyncFlags & 2) && _Responsible == NULL) {
           Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
        }

        // 满足条件则 park self
        if (_Responsible == Self || (SyncFlags & 1)) {
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            // Increase the RecheckInterval, but clamp the value.
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            TEVENT (Inflated enter - park UNTIMED) ;
            // 通过 park 将当前线程挂起,等待被唤醒
            Self->_ParkEvent->park() ;
        }

        if (TryLock(Self) > 0) break ;
        // 再次尝试自旋
        if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;
    }
    return ;
}

monitor释放

当某个持有锁的线程执行完同步代码块时,会释放锁并unpark后续线程

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
  
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     return ;
   }

      ObjectWaiter * w = NULL ;
      int QMode = Knob_QMode ;

    // 直接绕过 EntryList 队列,从 cxq 队列中获取线程用于竞争锁
      if (QMode == 2 && _cxq != NULL) {
          w = _cxq ;
          assert (w != NULL, "invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
    // cxq 队列插入 EntryList 尾部
      if (QMode == 3 && _cxq != NULL) {
          w = _cxq ;
          for (;;) {
             assert (w != NULL, "Invariant") ;
             ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
             if (u == w) break ;
             w = u ;
          }
          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          for (p = w ; p != NULL ; p = p->_next) {
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ;
              p->_prev = q ;
              q = p ;
          }

          ObjectWaiter * Tail ;
          for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
          if (Tail == NULL) {
              _EntryList = w ;
          } else {
              Tail->_next = w ;
              w->_prev = Tail ;
          }
      }

    // cxq 队列插入到_EntryList 头部
      if (QMode == 4 && _cxq != NULL) {
          // 把 cxq 队列放入 EntryList
          // 此策略确保最近运行的线程位于 EntryList 的头部
          w = _cxq ;
          for (;;) {
             assert (w != NULL, "Invariant") ;
             ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
             if (u == w) break ;
             w = u ;
          }
          assert (w != NULL              , "invariant") ;

          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          for (p = w ; p != NULL ; p = p->_next) {
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ;
              p->_prev = q ;
              q = p ;
          }

          if (_EntryList != NULL) {
              q->_next = _EntryList ;
              _EntryList->_prev = q ;
          }
          _EntryList = w ;
      }

      w = _EntryList  ;
      if (w != NULL) {
          assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
      w = _cxq ;
      if (w == NULL) continue ;

      for (;;) {
          assert (w != NULL, "Invariant") ;
          ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
          if (u == w) break ;
          w = u ;
      }

      if (QMode == 1) {
         // QMode == 1 : 把 cxq 倾倒入 EntryList 逆序
         ObjectWaiter * s = NULL ;
         ObjectWaiter * t = w ;
         ObjectWaiter * u = NULL ;
         while (t != NULL) {
             guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
             t->TState = ObjectWaiter::TS_ENTER ;
             u = t->_next ;
             t->_prev = u ;
             t->_next = s ;
             s = t;
             t = u ;
         }
         _EntryList  = s ;
         assert (s != NULL, "invariant") ;
      } else {
         // QMode == 0 or QMode == 2
         _EntryList = w ;
         ObjectWaiter * q = NULL ;
         ObjectWaiter * p ;
          // 将单向链表构造成双向环形链表;
         for (p = w ; p != NULL ; p = p->_next) {
             guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
             p->TState = ObjectWaiter::TS_ENTER ;
             p->_prev = q ;
             q = p ;
         }
      }

      if (_succ != NULL) continue;

      w = _EntryList  ;
      if (w != NULL) {
          guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
   }
}

notify线程唤醒

notify或者notifyAll方法可以唤醒同一个锁监视器下调用wait挂起的线程

void ObjectMonitor::notify(TRAPS) {
    CHECK_OWNER();
    if (_WaitSet == NULL) {
        TEVENT (Empty - Notify);
        return;
    }
    DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);

    int Policy = Knob_MoveNotifyee;

    Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
    ObjectWaiter *iterator = DequeueWaiter();
    if (iterator != NULL) {
        // 省略一些代码

         // 头插 EntryList
        if (Policy == 0) {
            if (List == NULL) {
                iterator->_next = iterator->_prev = NULL;
                _EntryList = iterator;
            } else {
                List->_prev = iterator;
                iterator->_next = List;
                iterator->_prev = NULL;
                _EntryList = iterator;
            }
        } else if (Policy == 1) {      // 尾插 EntryList
            if (List == NULL) {
                iterator->_next = iterator->_prev = NULL;
                _EntryList = iterator;
            } else {
                ObjectWaiter *Tail;
                for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);
                assert (Tail != NULL && Tail->_next == NULL, "invariant");
                Tail->_next = iterator;
                iterator->_prev = Tail;
                iterator->_next = NULL;
            }
        } else if (Policy == 2) {      // 头插 cxq
            // prepend to cxq
            if (List == NULL) {
                iterator->_next = iterator->_prev = NULL;
                _EntryList = iterator;
            } else {
                iterator->TState = ObjectWaiter::TS_CXQ;
                for (;;) {
                    ObjectWaiter *Front = _cxq;
                    iterator->_next = Front;
                    if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {
                        break;
                    }
                }
            }
        } else if (Policy == 3) {      // 尾插 cxq
            iterator->TState = ObjectWaiter::TS_CXQ;
            for (;;) {
                ObjectWaiter *Tail;
                Tail = _cxq;
                if (Tail == NULL) {
                    iterator->_next = NULL;
                    if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
                        break;
                    }
                } else {
                    while (Tail->_next != NULL) Tail = Tail->_next;
                    Tail->_next = iterator;
                    iterator->_prev = Tail;
                    iterator->_next = NULL;
                    break;
                }
            }
        } else {
            ParkEvent *ev = iterator->_event;
            iterator->TState = ObjectWaiter::TS_RUN;
            OrderAccess::fence();
            ev->unpark();
        }

        if (Policy < 4) {
            iterator->wait_reenter_begin(this);
        }
    }
    // 自旋释放
    Thread::SpinRelease(&_WaitSetLock);

    if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
        ObjectMonitor::_sync_Notifications->inc();
    }
}

synchronized原理图
在这里插入图片描述

保证可见性的原理

Synchronized的,即监视器锁规则:对同一个监视器的解锁,happens-before应对该监视器的加锁

public class MonitorDemo {
    private int a = 0;

    public synchronized void writer() {     // 1
        a++;                                // 2
    }                                       // 3

    public synchronized void reader() {    // 4
        int i = a;                         // 5
    }                                      // 6
}

在这里插入图片描述

在图中每一个箭头连接的两个节点就代表之间的happens-before关系。

  • 黑色的是通过程序顺序规则推导出来

  • 红色的为监视器锁规则推导而出:线程A释放锁happens-before线程B加锁

  • 蓝色的则是通过程序顺序规则和监视器锁规则推测出来happens-before关系,通过传递性规则进一步推导的happens-before关系

现在我们来重点关注2→happens-before→5,通过这个关系我们可以得出什么?

根据happens-before的定义中的一条:如果A→happens-before→B,则A的执行结果对B可见,并且A的执行顺序先于B。

线程A先对共享变量A进行加一,由2→happens-before→5关系可知线程A的执行结果对线程B可见,即线程B所读取到的a的值为1

锁优化

JVM中的锁优化

锁的优缺点对比

通过对各种类型的锁以及对锁优化的了解,对锁的优缺点有如下对比:

锁类型优点缺点使用场景
偏向锁加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距如果线程之间存在锁竞争,会带来额外的锁撤销的消耗有同步但无竞争的程序
轻量级锁竞争的线程不会阻塞,提高了响应速度如果线程始终得不到锁竞争的线程,使用自旋会消耗CPU的性能多线程交替执行同步块的情况下
重量级锁线程竞争不适用自旋,会消耗CPU线程阻塞,响应时间缓慢,在多线程下,频繁的获取锁和释放锁,会带来巨大的性能消耗锁竞争激烈的情况下

Synchronized与Lock

类别synchronizedLock
存在层次Java关键字,存在于JVM层面是一个类
锁的释放1. 以获取锁的线程执行完同步代码,释放锁
  1. 线程执行发生异常,JVM会让线程释放锁|由开发者控制锁的释放,一般在finally块中释放锁,不然会导致线程死锁|
    |锁的获取|假设线程A获得锁,线程B等待,如果线程A阻塞,B线程会一直等待|分情况而定,Lock有多个获取锁的方式,当前一个线程阻塞时,后一个线程可以不用一直等待|
    |锁状态|无法判断|可以判断|
    |锁类型|可重入、不可中断、非公平|可重入、可中断、可公平/非公平|
    |性能|少量同步|大量同步|

Condition和Lock的结合:ReentrantLock

多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获取锁,而不能中断,高并发情况下会导致性能下降,ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断

响应中断机制:当一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待,避免了死锁

总结

synchronized是通过软件(JVM)实现的,简单易用,即使在JDK5之后有了Lock,仍然被广泛的使用。

  • 使用Synchronized有哪些要注意的?

    • 锁对象不能为空,因为锁的信息都保存在对象头里

    • 作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错

    • 避免死锁

    • 在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错

  • synchronized是公平锁吗?

    synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carl·杰尼龟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值