在看源代码时遇到多线程需要同步的时候,总是会看见几种写法,修饰方法、修饰静态方法、
synchronized(Xxx.class)
、synchronized(this)
、synchronized(obj)
,之前一直没深究几种方式的区别,现在想来真是惊出一身冷汗,居然这个问题都没有仔细想清楚。
synchronized的语义
每个对象都有一个监视器monitor,被synchronized修饰,语义就是获取这个对象的monitor,反编译后可以看到monitorenter和monitorexit。synchronized关键字有三种应用方式(其实按标题来讲应该是5种,但是其中有两种都是与另外两种等价的):
- 修饰实例方法
- 修饰静态方法
- 修饰代码块(指定对象)
实验
先上代码做实验验证一下
public class SyncThread {
private final Object lock = new Object();
public void foo() throws Exception {
synchronized (SyncThread.class) {
for (int i = 0; i < 5; i++) {
System.out.println(">>>foo: " + i);
Thread.sleep(1000);
}
}
}
public void bar() throws Exception {
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println("<<<bar: " + i);
Thread.sleep(1000);
}
}
}
public void cpp() throws Exception {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println("===cpp: " + i);
Thread.sleep(1000);
}
}
}
public void der() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("!!!der: " + i);
Thread.sleep(1000);
}
}
}
可以看到有四种不同的synchronized修饰,以及一个没有同步的方法,再运行一下看看结果
public class ThreadApp {
public static void main(String[] args) {
final SyncThread syncThread = new SyncThread();
new Thread(() -> {
try {
syncThread.foo();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
syncThread.bar();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
syncThread.cpp();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
syncThread.der();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
可以看到结果
>>>foo: 0
<<<bar: 0
===cpp: 0
!!!der: 0
>>>foo: 1
===cpp: 1
<<<bar: 1
!!!der: 1
>>>foo: 2
!!!der: 2
<<<bar: 2
===cpp: 2
>>>foo: 3
<<<bar: 3
===cpp: 3
!!!der: 3
>>>foo: 4
!!!der: 4
<<<bar: 4
===cpp: 4
分析
从以上结果来看各线程并没有发生竞争,互不影响,其实明白了synchronized语义也很好理解以上结果,几个synchronized获取的monitor都不是一个,当然相互不影响。
但是值得注意的几点:
synchronized(Xxx.class)
获取的是类的monitor,所以与public synchronized static void some()
修饰静态方法是等价的synchronized(this)
获取的是当前实例的monitor,所以与public synchronized void some()
修饰实例方法是等价的
以上两点可以通过修改上述代码中方法可以很容易验证,我们修改最后一个方法
public synchronized static void der() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("!!!der: " + i);
Thread.sleep(1000);
}
}
运行得到结果
!!!der: 0
!!!der: 1
!!!der: 2
!!!der: 3
!!!der: 4
>>>foo: 0
>>>foo: 1
>>>foo: 2
>>>foo: 3
>>>foo: 4
可以看到,确实synchronized(Xxx.class)
与synchronized
修饰静态方法是等价的。再修改为synchronized
修饰实例方法
public synchronized void der() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("!!!der: " + i);
Thread.sleep(1000);
}
}
运行查看结果
<<<bar: 0
<<<bar: 1
<<<bar: 2
<<<bar: 3
<<<bar: 4
!!!der: 0
!!!der: 1
!!!der: 2
!!!der: 3
!!!der: 4
可以看到,确实synchronized(this)
与synchronized
修饰实例方法是等价的。
总之,个人认为要理解几种不一样的地方,关键是理解清楚是获取的谁的monitor,只要是同一个monitor,当然就会发生同步!