怎样在静态方法中使用 synchronized

Posted by liangfei on 2015-11-09

synchronized 的实现方式

synchronized 的类型可以分为两种:

  1. synchronized method
  2. synchronized block

两者的实现方式是不一样的,jvm 规范中写道,编译后的 synchronized method 会有一个 ACC_SYNCHRONIZED 的 flag,也就是说当 jvm 的方法调用指令(the method invocation instruction)从 the run-time constant pool 中查找到这个 method 的时候,已经知道它是一个synchronized method,所以锁操作是由方法调用以及返回指令来控制的。

synchronized block 的锁是由 monitorentermonitorexit 这两个指令来控制。

可以通过 javap 命令来“反汇编”一下 class 文件。

public class StaticMethodTest {
public static synchronized void staticMethod() { }
public static void staticMethod1() {
synchronized (StaticMethodTest.class) {
// ...
}
}
public void memberMethod() { }
}

编译成 class 文件后执行

javap -verbose StaticMethodTest

两个静态方法的输出结果分别为

public static synchronized void staticMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 2: 0
public static void staticMethod1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: ldc #2 // class StaticMethodTest
2: dup
3: astore_0
4: monitorenter
5: aload_0
6: monitorexit
7: goto 15
10: astore_1
11: aload_0
12: monitorexit
13: aload_1
14: athrow
15: return
Exception table:
from to target type
5 7 10 any
10 13 10 any

其实不管静态方法还是成员方法,synchronized 的实现方式都是一样的,那么类和对象究竟有什么关系呢?

类和对象

首先要了解的就是类究竟是怎么来的

JVM 拿到编译器编译好的 class 文件后,首先会把文件载入到内存中,class 文件当然会有自己的格式,所以需要由 ClassLoader 来解析文件的内容,这个解析出来的内容会用一个 Class 类的实例 - Class object 来表示,这个 object 可以通过 Java 的 ClassName.class来获取。

也就是说,Class object 是一个 Class 类型的实例(instance),而对象是一个 ClassName 的 instance。Class 和 ClassName都是类型,ClassName是由 class 关键字定义的,而Class是内置类型。

因此成员方法的synchronized method 就等价于 synchronized (this) block,即下面两种方式是等价的。

public synchronized void fun1() {
// do something here
}

public synchronized void fun2() {
synchronized (this) {
// do something here
}
}

成员方法是属于 this,而静态方法是属于 Class Object,那么静态方法的 synchronized method 也就等价于下面这种形式的 synchronized block 了。

public static synchronized void fun2() {
synchronized (ClassName.class) {
// do something here
}
}

验证代码

定义了三个静态方法,分别采用不同的锁机制,并且每个方法都是一个死循环。然后再定义三个线程分别执行调用三个方法。

import java.util.concurrent.*;

public class SynchronizedTest {
private static Object lock = new Object();

public synchronized static void fun() {
while (true) {
System.out.println("in fun");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void fun1() {
synchronized (SynchronizedTest.class) {
while (true) {
System.out.println("in fun1");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


public static void fun2() {
synchronized (lock) {
while (true) {
System.out.println("in fun2");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


public static void main(String[] args) {
new Thread() {
@Override
public void run() {
fun();
}
}.start();


new Thread() {
@Override
public void run() {
fun1();
}
}.start();


new Thread() {
@Override
public void run() {
fun2();
}
}.start();
}
}

运行结果就是 in fun 和 in fun2 交替出现,就是没有 in fun1。
只要运行 fun 的线程不交出锁,fun1 就无法被调用,因为他们都是共享了 Class object 的锁。


欢迎扫码关注 老梁写代码 微信公众号