记录最近在Kotlin的@Synchronized在伴生类中使用遇到的问题,代码如下。
// Kotlin类
class MyTest {
companion object {
@JvmStatic
@Synchronized
fun test1(){
try {
println("test1 start")
Thread.sleep(2000L)
println("test1 end")
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
fun test2(){
println("test2 start")
println("test2 end")
}
}
fun main(args: Array<String>) {
// 子线程1直接运行同步方法test1
Thread {
MyTest.test1()
}.start()
// 子线程2中运行同步代码块,同步代码块的锁为伴生类的class
Thread {
synchronized(MyTest.Companion::class.java){
MyTest().test2()
}
}.start()
}
// 预期结果:
// test1 start
// test1 end
// test2 start
// test2 end
// 实际结果:
// test1 start
// test2 start
// test2 end
// test1 end
从上面的运行结果可以看出子线程2的同步代码块使用的锁与MyTest.Companion.test1()所使用的锁是不一致的。我曾在子线程2中尝试过使用以下四个参数作为锁都是没办法达到预期效果:
1、MyTest::class
2、MyTest::class.java
3、MyTest.Companion::class
4、MyTest.Companion::class.java
因此,我将MyTest类通过字节码编译成Java类,查看里面的具体情况,发现MyTest.Companion.test1()使用的锁果然和上面几个不一样,Java代码如下。
// 通过字节码翻译成的Java类(已删减,只保留主要代码)
public final class MyTest {
@NotNull
public static final MyTest.Companion Companion = new MyTest.Companion((DefaultConstructorMarker)null);
public final void test2() {
String var1 = "test2 start";
System.out.println(var1);
var1 = "test2 end";
System.out.println(var1);
}
@JvmStatic
public static final synchronized void test1() {
Companion.test1();
}
public static final class Companion {
@JvmStatic
public final synchronized void test1() {
try {
String var1 = "test1 start";
System.out.println(var1);
Thread.sleep(2000L);
var1 = "test1 end";
System.out.println(var1);
} catch (InterruptedException var2) {
var2.printStackTrace();
}
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
1、MyTest内部生成了一个名为Companion的静态类,其内部有一个普通同步方法synchronized test1()
2、MyTest内持有MyTest.Companion类的实例名为Companion
3、MyTest还生成了一个静态同步方法static synchronized test1(),它的作用就是调用MyTest.Companion中的普通同步方法synchronized test1()
根据Java中synchronized关键字的规则可以得知,MyTest.test1()使用的锁是MyTest.class,而MyTest.Companion.test1()使用的锁则是MyTest.Companion的实例Companion。
看到这里我们也就知道为什么在kotlin代码的子线程2中使用的那四把锁无法达到我们的预期效果了,我们只需要将锁换成MyTest.Companion即可。
fun main(args: Array<String>) {
// 子线程1直接运行同步方法test1
Thread {
MyTest.test1()
}.start()
// 子线程2中运行同步代码块,同步代码块的锁为伴生类的实例
Thread {
synchronized(MyTest.Companion){
MyTest().test2()
}
}.start()
}
// 实际结果:
// test1 start
// test1 end
// test2 start
// test2 end