0
点赞
收藏
分享

微信扫一扫

再看 synchronized

芭芭蘑菇 2022-12-22 阅读 54


相关博客:

​​使用synchronized要注意的地方​​

先看这个示例:

package com.example.threaddesign;

/**
* @author Dongguabai
* @date 2018/12/4 17:33
*/
public class SynchronizedTest2 {

private static final Object LOCK = new Object();

public static void main(String[] args) {
TTe task2 = new TTe();
Thread t1 = new Thread(task2);
Thread t2 = new Thread(task2);
Thread t3 = new Thread(task2);
t1.start();
t2.start();
t3.start();
}
}

package com.example.threaddesign;

/**
* @author Dongguabai
* @date 2018/12/4 18:09
*/
public class TTe implements Runnable {

private final Object MONITOR = new Object();

@Override
public void run() {

while (true) {
//1
synchronized (MONITOR) {
try {
Thread.sleep(1000_1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

通过 jconsole 可以看到线程的一些信息:

再看 synchronized_f5

再看 synchronized_java_02

再看 synchronized_ide_03

可以看出是 Thread-0 获得了锁。

再使用 jstack 看看:

"Thread-2" #13 prio=5 os_prio=0 tid=0x0000000019476800 nid=0x18ec waiting for monitor entry [0x000000001a03f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.threaddesign.SynchronizedTest2$Task2.run(SynchronizedTest2.java:27)
- waiting to lock <0x00000000d61f5da8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)

"Thread-1" #12 prio=5 os_prio=0 tid=0x0000000019475800 nid=0x3708 waiting for monitor entry [0x0000000019f3f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.threaddesign.SynchronizedTest2$Task2.run(SynchronizedTest2.java:27)
- waiting to lock <0x00000000d61f5da8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000019473000 nid=0x27f8 waiting on condition [0x0000000019e3e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.example.threaddesign.SynchronizedTest2$Task2.run(SynchronizedTest2.java:27)
- locked <0x00000000d61f5da8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)

也可以看出是 Thread-0 获得了锁,那个锁是一个 monitor。

看看  TTe 的汇编指令:

汇编指令为:

C:\Users\Dongguabai>cd H:\idea_home\demoClient\src\main\java\com\example\threaddesign

C:\Users\Dongguabai>h:

H:\idea_home\demoClient\src\main\java\com\example\threaddesign>javac TTe.class
javac: 无效的标记: TTe.class
用法: javac <options> <source files>
-help 用于列出可能的选项

H:\idea_home\demoClient\src\main\java\com\example\threaddesign>javac TTe.java

H:\idea_home\demoClient\src\main\java\com\example\threaddesign>javap -c TTe
警告: 二进制文件TTe包含com.example.threaddesign.TTe
Compiled from "TTe.java"
public class com.example.threaddesign.TTe implements java.lang.Runnable {
public com.example.threaddesign.TTe();
Code:
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 MONITOR:Ljava/lang/Object;
15: return

public void run();
Code:
0: aload_0
1: getfield #3 // Field MONITOR:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: ldc2_w #4 // long 10001000l
10: invokestatic #6 // Method java/lang/Thread.sleep:(J)V
13: goto 21
16: astore_2
17: aload_2
18: invokevirtual #8 // Method java/lang/InterruptedException.printStackTrace:()V
21: aload_1
22: monitorexit
23: goto 31
26: astore_3
27: aload_1
28: monitorexit
29: aload_3
30: athrow
31: goto 0
Exception table:
from to target type
7 13 16 Class java/lang/InterruptedException
7 23 26 any
26 29 26 any
}

可以很明显的看到  monitorenter 和 monitorexit 指令。而且 monitorenter 和 monitorexit 是成对出现的(有些时候会出现一个monitorenter 对应多个 monitorexit,但是每一个 monitorexit 之前必有对应的 monitorenter,这是肯定的)。

monitorenter

每个对象都与一个 monitor 相关联,一个 monitor 的 lock 的锁只能被一个线程在同一时间获得,在一个线程尝试获得与对象关联 monitor 的所有权时会发生如下的几件事情。

  • 如果 monitor 的计数器为 0,则意味着该 monitor 的 lock 还没有被获得,某个线程获得之后将立即对该计数器加一,从此该线程就是这个 monitor 的所有者了。
  • 如果一个已经拥有该 monitor 所有权的线程重入,则会导致 monitor 计数器再次累加。
  • 如果monitor已经被其他线程所拥有,则其他线程尝试获取该 monitor 的所有权时,会被陷人阻塞状态直到 monitor 计数器变为 0,才能再次尝试获取对 monitor 的所有权。

monitorexit

释放对 monitor 的所有权,想要释放对某个对象关联的 monitor 的所有权的前提是,你曾经获得了所有权。释放 monitor 所有权的过程比较简单,就是将 monitor 的计数器减一,如果计数器的结果为 0,那就意味着该线程不再拥有对该 monitor 的所有权,通俗地讲就是解锁。与此同时被该 monitor block 的线程将再次尝试获得对该 monitor 的所有权。

使用 synchronized 要注意的地方

对​​使用synchronized要注意的地方​​的补充。

monitor 的对象不能为空

这个很好理解,每一个对象和 monitor 关联,如果对象为 null,monitor 根本就无从谈起。一个简单的例子:

package com.example.threaddesign;

/**
* @author Dongguabai
* @date 2018/12/5 14:36
*/
public class SynchronizedTest3 {

private static Object lock = null;

public static void main(String[] args) {
new Thread(()->{
add();
},"线程一").start();
new Thread(()->{
add();
},"线程二").start();
new Thread(()->{
add();
},"线程三").start();
}

public static void add(){
synchronized (lock){
System.out.println(Thread.currentThread().getName()+"-->进入了锁----");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->出锁----");
}
}
}

运行结果:

Exception in thread "线程一" Exception in thread "线程二" java.lang.NullPointerException
at com.example.threaddesign.SynchronizedTest3.add(SynchronizedTest3.java:24)
at com.example.threaddesign.SynchronizedTest3.lambda$main$0(SynchronizedTest3.java:13)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "线程三" java.lang.NullPointerException
at com.example.threaddesign.SynchronizedTest3.add(SynchronizedTest3.java:24)
at com.example.threaddesign.SynchronizedTest3.lambda$main$1(SynchronizedTest3.java:16)
at java.lang.Thread.run(Thread.java:745)
java.lang.NullPointerException
at com.example.threaddesign.SynchronizedTest3.add(SynchronizedTest3.java:24)
at com.example.threaddesign.SynchronizedTest3.lambda$main$2(SynchronizedTest3.java:19)
at java.lang.Thread.run(Thread.java:745)

synchronized 范围过大

由于 synchronized 关键字存在排他性,也就是说所有的线程必须串行地经过 synchronized 保护的共享区域,如果 synchronized 作用域越大,则代表着其效率越低,甚至还会丧失并发的优势。

举报

相关推荐

0 条评论