java并发synchronized 锁的膨胀过程(锁的升级过程)深入剖析(1)

我们先来说一下我们为什么需要锁?

因为在并发情况为了保证线程的安全性,是在一个多线程环境下正确性的概念,也就是保证多线程环境下共享的、可修改的状态的正确性(这里的状态指的是程序里的数据),在java程序中我们可以使用synchronized关键字来对程序进行加锁。

当声明synchronized代码块的时候,编译成的字节码将包含monitorenter指令 和 monitorexit指令。这两种指令均会消耗操作数栈上的一个引用类型的元素(也就是 synchronized 关键字括号里的引用),作为所要加锁解锁的锁对象。

(注意:jdk 1.6以前synchronized 关键字只表示重量级锁,1.6之后区分为偏向锁、轻量级锁、重量级锁。)

 

所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级:

  • 当没有竞争出现时,默认会使用偏向锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁可以降低无竞争开销。

  • 如果有另外的线程试图锁定某个已经被偏向过的对象,JVM 就需要撤销(revoke)偏向锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用轻量级锁;否则,进一步升级为重量级锁

 

那么我们来看段synchronized代码分析:

java代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

public class TestDemo {

}

public class DemoExample1 {

    static TestDemo testDemo;

    public static void main(String[] args) throws Exception {

        testDemo= new TestDemo();

        synchronized (testDemo){

            System.out.println("lock ing");

            testDemo.hashCode();

            System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());

        }

    }

}

 

运行并分析TestDemo.class文件命令:

1

javap -c DemoExample1.class

 

分析结果:

Compiled from "DemoExample1.java"

public class com.boke.DemoExample1 {

  static com.boke.TestDemo testDemo;

 

  public com.boke.DemoExample1();

    Code:

       0: aload_0

       1: invokespecial #1                  // Method java/lang/Object."<init>":()V

       4: return

 

  public static void main(java.lang.String[]) throws java.lang.Exception;

    Code:

       0: new           #2                  // class com/boke/TestDemo

       3: dup

       4: invokespecial #3                  // Method com/boke/TestDemo."<init>":()V

       7: putstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;

      10: getstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;

      13: dup

      14: astore_1

      15: monitorenter

      16: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;

      19: ldc           #6                  // String lock ing

      21: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

      24: getstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;

      27: invokevirtual #8                  // Method java/lang/Object.hashCode:()I

      30: pop

      31: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;

      34: getstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;

      37: invokestatic  #9                  // Method org/openjdk/jol/info/ClassLayout.parseInstance:(Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout;

      40: invokevirtual #10                 // Method org/openjdk/jol/info/ClassLayout.toPrintable:()Ljava/lang/String;

      43: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

      46: aload_1

      47: monitorexit

      48: goto          56

      51: astore_2

      52: aload_1

      53: monitorexit

      54: aload_2

      55: athrow

      56: return

    Exception table:

       from    to  target type

          16    48    51   any

          51    54    51   any

}

通过字节码可以看出包含一个monitorenter指令以及多个monitorexit指令。这是因为jvm需要确保所获得的锁在正常执行路径,以及异常执行路径上都能够被解锁。

 

我们可以抽象的理解为每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针

  • 当执行 monitorenter 时,如果目标锁对象的计数器为 0,那么说明它没有被其他线程所持有。在这个情况下,Java 虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加 1。
  • 在目标锁对象的计数器不为 0 的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加 1,否则需要等待,直至持有线程释放该锁。当执行 monitorexit 时,Java 虚拟机则需将锁对象的计数器减 1。当计数器减为 0 时,那便代表该锁已经被释放掉了。
  • 之所以采用这种计数器的方式,是为了允许同一个线程重复获取同一把锁。举个例子,如果一个 Java 类中拥有多个 synchronized 方法,那么这些方法之间的相互调用,不管是直接的还是间接的,都会涉及对同一把锁的重复加锁操作。因此,我们需要设计这么一个可重入的特性,来避免编程里的隐式约束。

  

我们来看一个案例:在不加锁的情况多下通过取两次数值然后进行对比,来模拟两次共享状态的操作:

java代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

public class DemoExample3 {

    public int sharedState;

 

    public void nonSafeAction() {

        while (sharedState < 100000) {

            int former = sharedState++;

            int latter = sharedState;

            if (former != latter - 1) {

                System.out.println("Observed data race, former is " +

                        former + ", " "latter is " + latter);

            }

        }

    }

  

    public static void main(String[] args) throws InterruptedException {

        final DemoExample3 demoExample3 = new DemoExample3();

        Thread thread1 = new Thread() {

            @Override

            public void run() {

                demoExample3.nonSafeAction();

            }

        };

   

        Thread thread2 = new Thread() {

            @Override

            public void run() {

                demoExample3.nonSafeAction();

            }

        };

 

        thread1.start();

        thread2.start();

        thread1.join();

        thread2.join();

    }

}

 

在没有加 synchronized 关键字的时候打印出来的结果(截取部分):

Observed data race, former is 55179, latter is 55181

Observed data race, former is 56752, latter is 56754

Observed data race, former is 58304, latter is 58306

Observed data race, former is 60340, latter is 60342

Observed data race, former is 61627, latter is 61629

Observed data race, former is 63107, latter is 62946

Observed data race, former is 64029, latter is 64029

Observed data race, former is 65579, latter is 65581

Observed data race, former is 67315, latter is 67317

Observed data race, former is 68542, latter is 68542

Observed data race, former is 70687, latter is 70687

Observed data race, former is 72654, latter is 72656

Observed data race, former is 74644, latter is 74646

就会发现,打印出好多与if (former != latter - 1) 条件相符的值,这是错误的,正确的结果应该是一条也没有;

 

我们在来看一下加上synchronized关键字的代码:

java代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

public class DemoExample3 {

    public int sharedState;

   

    public void nonSafeAction() {

        while (sharedState < 100000) {

            synchronized (this) {

                int former = sharedState++;

                int latter = sharedState;

                if (former != latter - 1) {

                    System.out.println("Observed data race, former is " +

                            former + ", " "latter is " + latter);

                }

            }

        }

    }

   

    public static void main(String[] args) throws InterruptedException {

        final DemoExample3 demoExample3 = new DemoExample3();

        Thread thread1 = new Thread() {

            @Override

            public void run() {

                demoExample3.nonSafeAction();

            }

        };

   

        Thread thread2 = new Thread() {

            @Override

            public void run() {

                demoExample3.nonSafeAction();

            }

        };

        thread1.start();

        thread2.start();

        thread1.join();

        thread2.join();

    }

}

 

这次看下加上synchronized关键字的打印出来的结果:

Process finished with exit code 0

 

说明将两次赋值过程用synchronized保护起来,使用this作为互斥单元,就可以避免别的线程并发的去修改 sharedState;这也就是我刚开说的并发情况下为了保证线程的安全性,我们可以通过加锁来保证。

 


说完我们为什么需要锁,接下来我们介绍偏向锁、轻量级锁、重量级锁及锁的膨胀过程

 

首先我们先从jvm源码中来分析锁的膨胀过程(锁升级的过程):

在jvm中synchronized的是行为是jvm runntime的一部分,所以我们需要先找到 Runtime 相关的功能实现。通过在代码中查询类似“monitor_enter”或“Monitor Enter”,很直观的就可以定位到:

sharedRuntime.cpp(http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/sharedRuntime.cpp),它是解释器和编译器运行时的基类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

// Handles the uncommon case in locking, i.e., contention or an inflated lock.

JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* _obj, BasicLock* lock, JavaThread* thread))

  // Disable ObjectSynchronizer::quick_enter() in default config

  // on AARCH64 and ARM until JDK-8153107 is resolved.

  if (ARM_ONLY((SyncFlags & 256) != 0 &&)

      AARCH64_ONLY((SyncFlags & 256) != 0 &&)

      !SafepointSynchronize::is_synchronizing()) {

    // Only try quick_enter() if we're not trying to reach a safepoint

    // so that the calling thread reaches the safepoint more quickly.

    if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;

  }

  // NO_ASYNC required because an async exception on the state transition destructor

  // would leave you with the lock held and it would never be released.

  // The normal monitorenter NullPointerException is thrown without acquiring a lock

  // and the model is that an exception implies the method failed.

  JRT_BLOCK_NO_ASYNC

  oop obj(_obj);

  if (PrintBiasedLockingStatistics) {

    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());

  }

  Handle h_obj(THREAD, obj);

  //在 JVM 启动时,我们可以指定是否开启偏向锁

  if (UseBiasedLocking) {

  // Retry fast entry if bias is revoked to avoid unnecessary inflation

 <strong> //fast_enter 是我们熟悉的完整锁获取路径</strong>

    ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);

  else {

  //slow_enter 则是绕过偏向锁,直接进入轻量级锁获取逻辑

    ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);

  }

  assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");

  JRT_BLOCK_END

JRT_END

 

synchronizer.cpp(https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp),JVM 同步相关的各种基础(不仅仅是 synchronized 的逻辑,包括从本地代码,也就是 JNI,触发的 Monitor 动作,全都可以在里面找到例如(jni_enter/jni_exit)):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

// -----------------------------------------------------------------------------

//  Fast Monitor Enter/Exit

// This the fast monitor enter. The interpreter and compiler use

// some assembly copies of this code. Make sure update those code

// if the following function is changed. The implementation is

// extremely sensitive to race condition. Be careful.

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,

                                    bool attempt_rebias, TRAPS) {

  if (UseBiasedLocking) {

    if (!SafepointSynchronize::is_at_safepoint()) {

      //biasedLocking定义了偏向锁相关操作,revoke_and_rebias revokeatsafepoint 则定义了当检测到安全点时的处理

      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);

      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {

        return;

      }

    else {

      assert(!attempt_rebias, "can not rebias toward VM thread");

      BiasedLocking::revoke_at_safepoint(obj);

    }

    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");

  }

  //如果获取偏向锁失败,则进入 slow_enter,锁升级

  slow_enter(obj, lock, THREAD);

}

  

// -----------------------------------------------------------------------------

// Interpreter/Compiler Slow Case

// This routine is used to handle interpreter/compiler slow case

// We don't need to use fast path here, because it must have been

// failed in the interpreter/compiler code.

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

  markOop mark = obj->mark();

  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

  if (mark->is_neutral()) {

    // Anticipate successful CAS -- the ST of the displaced mark must

    // be visible <= the ST performed by the CAS.

    // 将目前的 Mark Word 复制到 Displaced Header 上

    lock->set_displaced_header(mark);

    // 利用 CAS 设置对象的 Mark Wo

    if (mark == obj()->cas_set_mark((markOop) lock, mark)) {

      return;

    }

    // Fall through to inflate() …

    // 检查存在竞争

  else if (mark->has_locker() &&

             THREAD->is_lock_owned((address)mark->locker())) {

    assert(lock != mark->locker(), "must not re-lock the same lock");

    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock”);

    // 清除

    lock->set_displaced_header(NULL);

    return;

  }

  // The object header will never be displaced to this lock,

  // so it does not matter what the value is, except that it

  // must be non-zero to avoid looking like a re-entrant lock,

  // and must not look locked either.

  // 重置 Displaced Header

  lock->set_displaced_header(markOopDesc::unused_mark());

  //锁膨胀

  ObjectSynchronizer::inflate(THREAD,

                              obj(),

                              inflate_cause_monitor_enter)->enter(THREAD);

}

// This routine is used to handle interpreter/compiler slow case

// We don't need to use fast path here, because it must have

// failed in the interpreter/compiler code. Simply use the heavy

// weight monitor should be ok, unless someone find otherwise.

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {

  fast_exit(object, lock, THREAD);

}

  

//锁膨胀

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {

  // Inflate mutates the heap ...

  // Relaxing assertion for bug 6320749.

  assert (Universe::verify_in_progress() ||

          !SafepointSynchronize::is_at_safepoint(), "invariant") ;

  

  

  for (;;) {//自旋

      const markOop mark = object->mark() ;

      assert (!mark->has_bias_pattern(), "invariant") ;

  

      // The mark can be in one of the following states:

      // *  Inflated     - just return

      // *  Stack-locked - coerce it to inflated

      // *  INFLATING    - busy wait for conversion to complete

      // *  Neutral      - aggressively inflate the object.

      // *  BIASED       - Illegal.  We should never see this

   

      // CASE: inflated已膨胀,即重量级锁

      if (mark->has_monitor()) {//判断当前是否为重量级锁

          ObjectMonitor * inf = mark->monitor() ;//获取指向ObjectMonitor的指针

          assert (inf->header()->is_neutral(), "invariant");

          assert (inf->object() == object, "invariant") ;

          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");

          return inf ;

      }

   

      // CASE: inflation in progress - inflating over a stack-lock.膨胀等待(其他线程正在从轻量级锁转为膨胀锁)

      // Some other thread is converting from stack-locked to inflated.

      // Only that thread can complete inflation -- other threads must wait.

      // The INFLATING value is transient.

      // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.

      // We could always eliminate polling by parking the thread on some auxiliary list.

      if (mark == markOopDesc::INFLATING()) {

         TEVENT (Inflate: spin while INFLATING) ;

         ReadStableMark(object) ;

         continue ;

      }

   

      // CASE: stack-locked栈锁(轻量级锁)

      // Could be stack-locked either by this thread or by some other thread.

      //

      // Note that we allocate the objectmonitor speculatively, _before_ attempting

      // to install INFLATING into the mark word.  We originally installed INFLATING,

      // allocated the objectmonitor, and then finally STed the address of the

      // objectmonitor into the mark.  This was correct, but artificially lengthened

      // the interval in which INFLATED appeared in the mark, thus increasing

      // the odds of inflation contention.

      //

      // We now use per-thread private objectmonitor free lists.

      // These list are reprovisioned from the global free list outside the

      // critical INFLATING...ST interval.  A thread can transfer

      // multiple objectmonitors en-mass from the global free list to its local free list.

      // This reduces coherency traffic and lock contention on the global free list.

      // Using such local free lists, it doesn't matter if the omAlloc() call appears

      // before or after the CAS(INFLATING) operation.

      // See the comments in omAlloc().

  

      if (mark->has_locker()) {

          ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor

          // Optimistically prepare the objectmonitor - anticipate successful CAS

          // We do this before the CAS in order to minimize the length of time

          // in which INFLATING appears in the mark.

          m->Recycle();

          m->_Responsible  = NULL ;

          m->OwnerIsThread = 0 ;

          m->_recursions   = 0 ;

          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class

   

          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;

          if (cmp != mark) {//CAS失败//CAS失败,说明冲突了,自旋等待//CAS失败,说明冲突了,自旋等待//CAS失败,说明冲突了,自旋等待

             omRelease (Self, m, true) ;//释放监视器锁

             continue ;       // Interference -- just retry

          }

  

          // We've successfully installed INFLATING (0) into the mark-word.

          // This is the only case where 0 will appear in a mark-work.

          // Only the singular thread that successfully swings the mark-word

          // to 0 can perform (or more precisely, complete) inflation.

          //

          // Why do we CAS a 0 into the mark-word instead of just CASing the

          // mark-word from the stack-locked value directly to the new inflated state?

          // Consider what happens when a thread unlocks a stack-locked object.

          // It attempts to use CAS to swing the displaced header value from the

          // on-stack basiclock back into the object header.  Recall also that the

          // header value (hashcode, etc) can reside in (a) the object header, or

          // (b) a displaced header associated with the stack-lock, or (c) a displaced

          // header in an objectMonitor.  The inflate() routine must copy the header

          // value from the basiclock on the owner's stack to the objectMonitor, all

          // the while preserving the hashCode stability invariants.  If the owner

          // decides to release the lock while the value is 0, the unlock will fail

          // and control will eventually pass from slow_exit() to inflate.  The owner

          // will then spin, waiting for the 0 value to disappear.   Put another way,

          // the 0 causes the owner to stall if the owner happens to try to

          // drop the lock (restoring the header from the basiclock to the object)

          // while inflation is in-progress.  This protocol avoids races that might

          // would otherwise permit hashCode values to change or "flicker" for an object.

          // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.

          // 0 serves as a "BUSY" inflate-in-progress indicator

          // fetch the displaced mark from the owner's stack.

          // The owner can't die or unwind past the lock while our INFLATING

          // object is in the mark.  Furthermore the owner can't complete

          // an unlock on the object, either.

          markOop dmw = mark->displaced_mark_helper() ;

          assert (dmw->is_neutral(), "invariant") ;

          //CAS成功,设置ObjectMonitor的_header、_owner和_object等

          // Setup monitor fields to proper values -- prepare the monitor

          m->set_header(dmw) ;

 

          // Optimization: if the mark->locker stack address is associated

          // with this thread we could simply set m->_owner = Self and

          // m->OwnerIsThread = 1. Note that a thread can inflate an object

          // that it has stack-locked -- as might happen in wait() -- directly

          // with CAS.  That is, we can avoid the xchg-NULL .... ST idiom.

          m->set_owner(mark->locker());

          m->set_object(object);

          // TODO-FIXME: assert BasicLock->dhw != 0.

  

          // Must preserve store ordering. The monitor state must

          // be stable at the time of publishing the monitor address.

          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;

          object->release_set_mark(markOopDesc::encode(m));

   

          // Hopefully the performance counters are allocated on distinct cache lines

          // to avoid false sharing on MP systems ...

          if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;

          TEVENT(Inflate: overwrite stacklock) ;

          if (TraceMonitorInflation) {

            if (object->is_instance()) {

              ResourceMark rm;

              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",

                (void *) object, (intptr_t) object->mark(),

                object->klass()->external_name());

            }

          }

          return m ;

      }

   

      // CASE: neutral 无锁

      // TODO-FIXME: for entry we currently inflate and then try to CAS _owner.

      // If we know we're inflating for entry it's better to inflate by swinging a

      // pre-locked objectMonitor pointer into the object header.   A successful

      // CAS inflates the object *and* confers ownership to the inflating thread.

      // In the current implementation we use a 2-step mechanism where we CAS()

      // to inflate and then CAS() again to try to swing _owner from NULL to Self.

      // An inflateTry() method that we could call from fast_enter() and slow_enter()

      // would be useful.

  

      assert (mark->is_neutral(), "invariant");

      ObjectMonitor * m = omAlloc (Self) ;

      // prepare m for installation - set monitor to initial state

      m->Recycle();

      m->set_header(mark);

      m->set_owner(NULL);

      m->set_object(object);

      m->OwnerIsThread = 1 ;

      m->_recursions   = 0 ;

      m->_Responsible  = NULL ;

      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class

   

      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {

          m->set_object (NULL) ;

          m->set_owner  (NULL) ;

          m->OwnerIsThread = 0 ;

          m->Recycle() ;

          omRelease (Self, m, true) ;

          m = NULL ;

          continue ;

          // interference - the markword changed - just retry.

          // The state-transitions are one-way, so there's no chance of

          // live-lock -- "Inflated" is an absorbing state.

      }

   

      // Hopefully the performance counters are allocated on distinct

      // cache lines to avoid false sharing on MP systems ...

      if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;

      TEVENT(Inflate: overwrite neutral) ;

      if (TraceMonitorInflation) {

        if (object->is_instance()) {

          ResourceMark rm;

          tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",

            (void *) object, (intptr_t) object->mark(),

            object->klass()->external_name());

        }

      }

      return m ;

  }

}

  

膨胀过程的实现比较复杂,大概实现过程如下:

1、整个膨胀过程在自旋下完成;

2、mark->has_monitor()方法判断当前是否为重量级锁,即Mark Word的锁标识位为 10,如果当前状态为重量级锁,执行步骤(3),否则执行步骤(4);

3、mark->monitor()方法获取指向ObjectMonitor的指针,并返回,说明膨胀过程已经完成;

4、如果当前锁处于膨胀中,说明该锁正在被其它线程执行膨胀操作,则当前线程就进行自旋等待锁膨胀完成,这里需要注意一点,虽然是自旋操作,但不会一直占用cpu资源,每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起;如果其他线程完成锁的膨胀操作,则退出自旋并返回;

5、如果当前是轻量级锁状态,即锁标识位为 00,膨胀过程如下:

  •     通过omAlloc方法,获取一个可用的ObjectMonitor monitor,并重置monitor数据;
  •     通过CAS尝试将Mark Word设置为markOopDesc:INFLATING,标识当前锁正在膨胀中,如果CAS失败,说明同一时刻其它线程已经将Mark Word设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成;
  •     如果CAS成功,设置monitor的各个字段:_header、_owner和_object等,并返回;

6、如果是无锁,重置监视器值;

 

以上就是从jvm源码来分析锁的膨胀过程了。


 

接下来我们案例入手开始分析偏向锁(批量重偏向、批量撤销)、轻量级锁、重量级锁及膨胀过程:

 

偏向锁:

  • 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。

  • 在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。

  • 当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。

  • 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

  • 偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。

 

在上篇【java并发笔记三之synchronized 偏向锁 轻量级锁 重量级锁证明】说过偏向锁在没有禁止延迟的时候还没加锁之前就已经是偏向锁了,但是加锁完之后,退出同步代码块 还是偏向锁;计算过hashcode之后就不能被偏向。

一、我们来看段代码证明下,在没有计算hashcode的情况下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

//创建一个啥都没有的类:

public class TestDemo {}

  

public class DemoExample {

    static TestDemo testDemo;

    public static void main(String[] args) throws Exception {

        //此处睡眠50000ms,取消jvm默认偏向锁延迟4000ms

        Thread.sleep(5000);

        testDemo= new TestDemo();

  

        //hash计算?

        //testDemo.hashCode();

  

        System.out.println("befor lock");

        //无锁:偏向锁?

        System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());

  

        synchronized (testDemo){

            System.out.println("lock ing");

            System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());

        }

  

        System.out.println("after lock");

        System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());

    }

}

 

运行结果:

befor lock

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

lock ing

com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763)

      4     4        (object header)                           8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

after lock

com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763)

      4     4        (object header)                           8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

分析结果:

befor lock:绿颜色表示:虽然是偏向锁,但是黄颜色表示没有任何线程持有锁(一个对象被初始化的时候是可偏向的)

lock  ing: 绿颜色表示偏向锁,黄颜色的表示当前线程拿到锁

after lock:绿颜色表示偏向锁,黄颜色的表示当前线程拿到锁,还是偏向的状态;(偏向锁退出锁后依然是偏向状态)

 

jvm在初始化一个对象的时候,如果没有启用偏向锁延迟,就会去判断这个对象是否可以被偏向,如果可以就是偏向锁;退出同步代码块 还是偏向锁。

 

二、在对象进行hashcode计算之后就会输出下面的结果(也就是代码的这块testDemo.hashCode()去掉注释,进行hashcode运算):

befor lock

com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

lock ing

com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           f8 28 4b 0c (11111000 00101000 01001011 00001100) (206252280)

      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

after lock

com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763)

      4     4        (object header)                           8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

结果显示并不是偏向锁了,说明对象在计算过hashcode之后就不能被偏向;

  1. 具体来说,在线程进行加锁时,如果该锁对象支持偏向锁,那么 Java 虚拟机会通过 CAS操作,将当前线程的地址记录在锁对象的标记字段之中,并且将标记字段的最后三位设置为:1 01;

  2. 在接下来的运行过程中,每当有线程请求这把锁,Java 虚拟机只需判断锁对象标记字段中:最后三位是否为: 1 01,是否包含当前线程的地址,以及 epoch 值是否和锁对象的类的epoch 值相同。如果都满足,那么当前线程持有该偏向锁,可以直接返回;

 

这里的 epoch 值是一个什么概念呢?

  • 我们先从偏向锁的撤销讲起。当请求加锁的线程和锁对象标记字段保持的线程地址不匹配时(而且 epoch 值相等,如若不等,那么当前线程可以将该锁重偏向至自己),Java 虚拟机需要撤销该偏向锁。这个撤销过程非常麻烦,它要求持有偏向锁的线程到达安全点,再将偏向锁替换成轻量级锁;

  • 如果某一类锁对象的总撤销数超过了一个阈值(对应 jvm参数 -XX:BiasedLockingBulkRebiasThreshold,默认为 20),那么 Java 虚拟机会宣布这个类的偏向锁失效;(这里说的就是批量重偏向)

       JVM源码:

1

2

3

4

5

product(intx, BiasedLockingBulkRebiasThreshold, 20,                       \

        "Threshold of number of revocations per type to try to "          \

        "rebias all objects in the heap of that type")                    \

        range(0, max_intx)                                                \

        constraint(BiasedLockingBulkRebiasThresholdFunc,AfterErgo)        \

 

  • 具体的做法便是在每个类中维护一个 epoch 值,你可以理解为第几代偏向锁。当设置偏向锁时,Java 虚拟机需要将该 epoch 值复制到锁对象的标记字段中;

  • 在宣布某个类的偏向锁失效时,Java 虚拟机实则将该类的 epoch 值加 1,表示之前那一代的偏向锁已经失效。而新设置的偏向锁则需要复制新的 epoch 值;

  • 为了保证当前持有偏向锁并且已加锁的线程不至于因此丢锁,Java 虚拟机需要遍历所有线程的 Java 栈,找出该类已加锁的实例,并且将它们标记字段中的 epoch 值加 1。该操作需要所有线程处于安全点状态;

  • 如果总撤销数超过另一个阈值(对应 jvm 参数 -XX:BiasedLockingBulkRevokeThreshold,默认值为 40),那么 Java 虚拟机会认为这个类已经不再适合偏向锁。此时,Java 虚拟机会撤销该类实例的偏向锁,并且在之后的加锁过程中直接为该类实例设置轻量级锁(这里说的就是偏向批量撤销)

      

JVM源码:

1

2

3

4

5

product(intx, BiasedLockingBulkRevokeThreshold, 40,                       \

        "Threshold of number of revocations per type to permanently "     \

        "revoke biases of all objects in the heap of that type")          \

        range(0, max_intx)                                                \

        constraint(BiasedLockingBulkRevokeThresholdFunc,AfterErgo)  

  


 

接下来我们分析两个批量重偏向相关案例(禁止偏向锁延迟的情况下:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0):

 

案例一:

java代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

public class TestDemo {

}

public class DemoExample4 {

    public static void main(String[] args) throws InterruptedException {

        test1();

    }

   

   public class DemoExample5 {

    public static void main(String[] args) throws InterruptedException {

        test1();

    }

   

    /**

     * 仅证明批量重偏向

     * @throws InterruptedException

     */

    public  static  void test1() throws InterruptedException {

        List<TestDemo> list = new ArrayList<>();

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

            list.add(new TestDemo());

        }

        Thread t1 = new Thread(()->{

            System.out.println("加锁前 get(0) 应该是无锁可偏向 "+ ClassLayout.parseInstance(list.get(0)).toPrintable());

            for (TestDemo a:list  ) {

                synchronized (a){

                    System.out.print("加锁 >");

                }

            }

            System.out.println();

            System.out.println("加锁后 get(0) 应该是偏向锁"+ClassLayout.parseInstance(list.get(0)).toPrintable());

            try {

                TimeUnit.SECONDS.sleep(1000);//这里不让线程死,防止线程ID复用

            catch (InterruptedException e) {

                e.printStackTrace();

            }

        });

        t1.start();

        TimeUnit.SECONDS.sleep(5);

        Thread t2 = new Thread(()->{

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

                TestDemo a = list.get(i);

                synchronized (a){

                    System.out.print("加锁 >");

                }

                if (i==18){

                    System.out.println();

                    System.out.println("加锁后 get(18) 应该是无锁(轻量级锁释放) "+ClassLayout.parseInstance(list.get(i)).toPrintable());

                }

                if (i==19){ //开始重偏向

                    System.out.println();

                    System.out.println("加锁后 get(19) 应该是偏向锁 "+ClassLayout.parseInstance(list.get(i)).toPrintable());

                    System.out.println("加锁后 get(0) 应该是无锁(轻量级锁释放) "+ClassLayout.parseInstance(list.get(0)).toPrintable());

                    System.out.println("加锁后 get(99) 应该是偏向锁 偏向t1 "+ClassLayout.parseInstance(list.get(99)).toPrintable());

                }

                if (i==20){

                    System.out.println();

                    System.out.println("加锁后 get(20) 应该是偏向锁 "+ClassLayout.parseInstance(list.get(i)).toPrintable());

                }

            }

        });

        t2.start();

    }

}

 

运行并分析结果:

com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >

 

加锁后 get(0) 应该是偏向锁com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 30 8a 73 (00000101 00110000 10001010 01110011) (1938436101)

      4     4        (object header)                           c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >

加锁后 get(18) 应该是无锁(轻量级锁释放) com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁 >

加锁后 get(19) 应该是偏向锁 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 41 0b 75 (00000101 01000001 00001011 01110101) (1963671813)

      4     4        (object header)                           c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁后 get(0) 应该是无锁(轻量级锁释放) com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁后 get(99) 应该是偏向锁 偏向t1 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 30 8a 73 (00000101 00110000 10001010 01110011) (1938436101)

      4     4        (object header)                           c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁 >

加锁后 get(20) 应该是偏向锁 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 41 0b 75 (00000101 01000001 00001011 01110101) (1963671813)

      4     4        (object header)                           c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708)

      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >加锁 >

 

案例二:

java代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

public class TestDemo {

}

public class DemoExample7 {

    public static void main(String[] args) throws Exception {

  

        List<TestDemo> list = new ArrayList<>();

        //初始化数据

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

            list.add(new TestDemo());

        }

   

        Thread t1 = new Thread() {

            String name = "1";

            public void run() {

                System.out.printf(name);

                for (TestDemo a : list) {

                    synchronized (a) {

                        if (a == list.get(10)) {

                            System.out.println("t1 预期是偏向锁" 10 + ClassLayout.parseInstance(a).toPrintable());

                        }

                    }

                }

                try {

                    Thread.sleep(100000);

                catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        };

        t1.start();

        Thread.sleep(5000);

        System.out.println("main 预期是偏向锁" 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());

   

        Thread t2 = new Thread() {

            String name = "2";

  

            public void run() {

                System.out.printf(name);

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

                    TestDemo a = list.get(i);

                    // hack 为了在批量重偏向发生后再次加锁,前面使用了轻量级锁的对象

                    if (i == 20) {

                        a = list.get(9);

                    }

   

                    synchronized (a) {

                        if (i == 10) {

                            //已经经过偏向锁撤销,并使用轻量级锁的对象,释放后  状态依为001 无锁状态

                            System.out.println("t2 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list.get(1)).toPrintable());

                            //因为和t1交替使用对象a 没有发生竞争,但偏向锁已偏向,另外不满足重偏向条件,所以使用轻量级锁

                            System.out.println("t2 i=10 get(i) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());

                        }

                        if (i == 19) {

                            //已经经过偏向锁撤销,并使用轻量级锁的对象,在批量重偏向发生后。不会影响现有的状态  状态依然为001

                            System.out.println("t2  i=19  get(10)预期是无锁" 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());

                            //满足重偏向条件后,已偏向的对象可以重新使用偏向锁 将线程id指向当前线程,101

                            System.out.println("t2  i=19  get(i) 满足重偏向条件20 预期偏向锁 " + i + ClassLayout.parseInstance(a).toPrintable());

                            //满足重偏向条件后,已偏向还为需要加锁的对象依然偏向线程1 因为偏向锁的撤销是发生在下次加锁的时候。这里没有执行到同步此对象,所以依然偏向t1

                            System.out.println("t2  i=19  get(i) 满足重偏向条件20 但后面的对象没有被加锁,所以依旧偏向t1 " + i + ClassLayout.parseInstance(list.get(40)).toPrintable());

                        }

                        if (i == 20) {

                            //满足重偏向条件后,再次加锁之前使用了轻量级锁的对象,依然轻量级锁,证明重偏向这个状态只针对偏向锁。已经发生锁升级的,不会退回到偏向锁

                            System.out.println("t2  i=20 满足偏向条件之后,之前被设置为无锁状态的对象,不可偏向,这里使用的是轻量级锁  get(9)预期是轻量级锁 " + ClassLayout.parseInstance(a).toPrintable());

                        }

                    }

                }

                try {

                    Thread.sleep(100000);

                catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        };

        t2.start();

        Thread.sleep(5000);

    }

}

 

运行并分析结果:

t1 预期是偏向锁10  com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947)

      4     4        (object header)                           f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

main 预期是偏向锁 10 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947)

      4     4        (object header)                           f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

2t2 i=10 get(1)预期是无锁 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

t2 i=10 get(i) 预期轻量级锁 10 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           08 69 42 08 (00001000 01101001 01000010 00001000) (138569992)

      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

t2  i=19  get(10)预期是无锁10com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

t2  i=19  get(i) 满足重偏向条件20 预期偏向锁 19com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 71 95 ae (00000101 01110001 10010101 10101110) (-1365937915)

      4     4        (object header)                           f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

t2  i=19  get(i) 满足重偏向条件20 但后面的对象没有被加锁,所以依旧偏向t1 19com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947)

      4     4        (object header)                           f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

t2  i=20 满足偏向条件之后,之前被设置为无锁状态的对象,不可偏向,这里使用的是轻量级锁  get(9)预期是轻量级锁 com.boke.TestDemo object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           08 69 42 08 (00001000 01101001 01000010 00001000) (138569992)

      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)

      8     4        (object header)                           05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386488.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MSCRM二次开发实现自动编号功能

功能描述&#xff1a;对客户实体实现自动编号功能&#xff0c;1、2、3、4...... 自动编号存放于属性accountnumber.原  理&#xff1a;在mscrm服务器用一个文本文件存放当前最新编号&#xff0c;每当创建客户记录时在PreCreate事件接口做以下步骤&#xff1a;1、锁定文本文件…

这篇文章可以满足你80%日常工作!成功入职腾讯

什么是中年危机 根据权威数据显示&#xff0c;国内IT程序员鼎盛时期是在25-27岁左右&#xff0c;30岁对于程序员而言完全是一个38线&#xff0c;接着就是转业转岗的事情&#xff0c;这一点在业界也算是一个共识了。 大学毕业步入IT行业普遍年龄也是在22岁左右&#xff0c;然而…

java并发synchronized 锁的膨胀过程(锁的升级过程)深入剖析(2)

接下来我们分析两个批量偏向撤销的相关案例&#xff08;禁止偏向锁延迟的情况下&#xff1a;-XX:UseBiasedLocking -XX:BiasedLockingStartupDelay0&#xff09;&#xff1a; 案例一&#xff1a; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28…

System.Configuration命名空间下的关键类

1.ConfigurationManager和 WebConfigurationManager类&#xff1a; 使用 ConfigurationManager 类&#xff0c;可以访问计算机和应用程序的配置信息。ConfigurationManager 是处理客户端应用程序配置文件的首选方法&#xff1b;不推荐使用任何其他方法。对于 Web 应用程序&…

连续四年百度Android岗必问面试题!Android校招面试指南

前言 刚从阿里面试回来&#xff0c;想和大家分享一些我的面试经验&#xff0c;以及面试题目。 这篇文章将会更加聚焦在面试前需要看哪些资料&#xff0c;一些面试技巧以及一些这次的面试考题。 面试经历 7月确定想走后开始看各种面经&#xff0c;复习基础知识&#xff0c;月…

Spring Boot教程(11) – 理解注解@ControllerAdvice

之前&#xff0c;我们介绍过ModelAttribute和ExceptionHandler,前者可以往请求的Model里加数据&#xff0c;后者可以接受请求处理方法抛出的异常。但是他们放在控制器(Controller)里的时候&#xff0c;作用范围是有限的&#xff0c;只管当前控制器里的方法。如果你有几百个控制…

透彻解析!字节跳动Android实习面试凉凉经,年薪超过80万!

什么是Kotlin? Kotlin&#xff0c;如前面所说&#xff0c;它是JetBrains开发的基于JVM的语言。JetBrains因为创造了一个强大的Java开发IDE被大家所熟知。Android Studio&#xff0c;官方的Android IDE&#xff0c;就是基于Intellij&#xff0c;作为一个该平台的插件。 Kotli…

synchronized 底层如何实现?什么是锁升级、降级?

synchronized 底层如何实现&#xff1f;什么是锁升级、降级&#xff1f; synchronized 代码块是由一对 monitorenter/monitorexit 指令实现的&#xff0c;Monitor 对象是同步的基本实现单元。 https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#d5e13622 在Jav…

Spring主要用到两种设计模式

Spring主要用到两种设计模式 1、工厂模式 Spring容器就是实例化和管理全部Bean的工厂。 工厂模式可以将Java对象的调用者从被调用者的实现逻辑中分离出来。 调用者只关心被调用者必须满足的某种规则&#xff0c;这里的规则我们可以看做是接口&#xff0c;而不必关心实例的具体实…

意外收获字节跳动内部资料,已开源

前言 每年的3、4月份是各大企业为明年拓展业务大量吸纳人才的关键时期&#xff0c;招聘需求集中、空缺岗位多&#xff0c;用人单位也习惯在初秋进行大规模招聘。 金九银十&#xff0c;招聘旺季&#xff0c;也是一个求职旺季。 不打无准备的仗&#xff0c;在这种关键时期&…

OpenJDK研究

这里以32位Windows 7为例 安装必须的软件 JDK1.8CygwinMicrosoft Visual Studio 2010 (请下载英文版) 这里就不介绍怎么安装这些软件了&#xff0c;假设安装后的目录名分别是: (请根据你的实际情况调整这些目录名&#xff09; D:\JavaSE1.8 D:\Cygwin D:\VS2010 增加环境变…

猎头文章(一)

&#xff08;一&#xff09;从来没有想过自己会加入这一行&#xff0c; 从开始自己喜欢的专业通讯&#xff0c; 到后来喜欢的管理&#xff0c;&#xff0c;幻想过是专业高手&#xff0c; 幻想过管理专家&#xff0c; 却从来没有想过进入这一行&#xff0c;但 真的在我刚刚离开校…

成功跳槽百度工资从15K涨到28K,威力加强版

前言 看到一篇文章中提到“最近几年国内的初级Android程序员已经很多了&#xff0c;但是中高级的Android技术人才仍然稀缺“&#xff0c;这的确不假&#xff0c;从我在百度所进行的一些面试来看&#xff0c;找一个适合的高级Android工程师的确不容易&#xff0c;一般需要进行大…

Redis下载及安装(windows版)

下载地址 1、Github下载地址&#xff1a;https://github.com/MicrosoftArchive/redis/releases 2、百度网盘下载地址 https://pan.baidu.com/s/1z1_OdNVbtgyEjiktqgB83g 密码&#xff1a;kdfq 安装过程 1.首先先把下载的压缩包解压到一个文件夹中 2.打开cmd指令窗口 3.输入你刚…

成功跳槽百度工资从15K涨到28K,跳槽薪资翻倍

前言 这篇文章主要是分享今年上半年的面试心得&#xff0c;现已就职于某大厂有三个月了&#xff0c;近期有很多公司均已启动秋招&#xff0c;也祝大家在 2020 的下半年面试顺利&#xff0c;获得理想的offer&#xff01; 之前找工作的那段时间感想颇多&#xff0c;总结一点面试…

分布式锁RedLock的java实现Redisson

1. 概述Redisson是一个在Redis的基础上实现的Java驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。它不仅提供了一系列的分布式的Java常用对象&#xff0c;还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue,…

我三年开发经验,从字节跳动抖音离职后,看看这篇文章吧!

最新BAT大厂面试者整理的Android面试题目&#xff01; 近期根据网友分享大厂面试题目&#xff0c;今天我将网友面试的BAT等大厂Android面试题目整理出来&#xff0c;希望能够帮助大家&#xff01; 珍藏版&#xff08;1&#xff09;——高级 UI 晋升 第一节、触摸事件分发机制…

arthas命令redefine实现Java热更新

Arthas非常重要的命令redefine&#xff0c;主要作用是加载外部的.class文件&#xff0c;用来替换JVM已经加载的类&#xff0c;总结起来就是实现了Java的热更新。 redefine在一下几种情况中会失败&#xff1a;1、增加了field&#xff1b;2、增加了method&#xff1b;3、替换正在…

安装 ZendServer-CE 可能遇到的问题

安装后,打开http://localhost:10081/ZendServer 出现如下页面. Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request. Please contact the server administrator, adminexample.com and inform them…

Elk7.2 Docker

正如官方所说的那样 https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html&#xff0c;Elasticsearch默认使用mmapfs目录来存储索引。操作系统默认的mmap计数太低可能导致内存不足&#xff0c;我们可以使用下面这条命令来增加内存 为了防止…