相关博客:
使用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 可以看到线程的一些信息:
可以看出是 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
可以很明显的看到 monitorenter 和 monitorexit 指令。而且 monitorenter 和 monitorexit 是成对出现的(有些时候会出现一个monitorenter 对应多个 monitorexit,但是每一个 monitorexit 之前必有对应的 monitorenter,这是肯定的)。
monitorenter
每个对象都与一个 monitor 相关联,一个 monitor 的 lock 的锁只能被一个线程在同一时间获得,在一个线程尝试获得与对象关联 monitor 的所有权时会发生如下的几件事情。
monitorexit
释放对 monitor 的所有权,想要释放对某个对象关联的 monitor 的所有权的前提是,你曾经获得了所有权。释放 monitor 所有权的过程比较简单,就是将 monitor 的计数器减一,如果计数器的结果为 0,那就意味着该线程不再拥有对该 monitor 的所有权,通俗地讲就是解锁。与此同时被该 monitor block 的线程将再次尝试获得对该 monitor 的所有权。
对使用synchronized要注意的地方的补充。
这个很好理解,每一个对象和 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 作用域越大,则代表着其效率越低,甚至还会丧失并发的优势。