今天写一段程序,里面有一个锁的嵌套,在里面,我wait释放了一个锁,另一个锁并没有释放,但是在运行的时候,没有释放的锁竟然能够获得,
示例代码如下:
public class TestSyncOnString {
public static class Worker implements Runnable {
public String lock1;
public String lock2;
public Worker(String lock1, String lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
public void run() {
// 获得lock1
synchronized (lock1) {
System.out.println("gain lock1");
// 获得lock2
synchronized (lock2) {
System.out.println("gain lock2");
while (true) {
try {
System.out.println("release lock2");
// 释放lock2的锁
lock2.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("the end of synchronized code locked by lockAfter!");
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
String lock1 = "lock";
String lock2 = "lock";
Worker worker = new Worker(lock1, lock2);
new Thread(worker).start();
Thread.sleep(100);
synchronized (lock1) {
System.out.println("gain lock1");
}
}
}
在这段代码的Worker类的run方法中,我们先获得了lock1的锁,后获得了lock2的锁,然后释放了lock2的锁,并让线程等待,在main thread中,我们尝试获得lock1的锁,程序输出如下:
gain lock1
gain lock2
release lock2
gain lock1
Process finished with exit code 130
问题:这段代码会打印出gain lock1,表明lock1的锁也能够获得,这就比较奇怪,因为在Worker的run方法中,只释放了lock2的锁,未释放lock1的锁,但main thread确实表明将lock1的锁释放了。
上述类中,lock1和lock2变量的值都是"lock",我们将lock1改为"lock1",lock2改为"lock2",然后再运行一遍程序,输出结果如下
gain lock1
gain lock2
release lock2
这时候程序无法再获得lock1的锁,block在了获取lock1的锁的时候。
在2014年,笔者刚工作两年的时候,有一项猜测的结论,即变量lock1与变量lock2其实是都是从字符串常量池中获得的同一个对象,这就导致了当先锁lock1后锁lock2的时候,其实锁的是同一个对象,在lock2释放了之后,lock1的锁也被释放了(因为是同一个),这时再对lock1加锁就能够获得了。
2023年03-04日,验证一下想法
public class TestSyncOnString {
public static class Worker implements Runnable {
public String lock;
public Worker(String lock) {
this.lock = lock;
}
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 获得lock1
synchronized (lock){
lock.notifyAll();
}
}
}
public static void main(String[] args) throws InterruptedException {
String lock1 = "lock";
String lock2 = "lock";
new Thread(new Worker(lock2)).start();
synchronized (lock1){
lock1.wait();
System.out.println("lock1 be notified");
}
}
}
在这个代码中,main thread sync 了 lock1,并且wait,Worker 这个thread使用lock2进行了notifyAll操作,lock1与lock2的值相同,都是lock字符串,输出结果如下
lock1 be notified
Process finished with exit code 0
lock1的wait被notify了,从这个实验上侧面证明了lock1与lock2是一个对象。
--------------------------------------------------------------
可以用更简单的一个方式验证这两个对象是一个对象
public static void main(String[] args) throws InterruptedException {
String lock1 = "lock";
String lock2 = "lock";
System.out.println(lock2 == lock1);
}
java中等号对比的是是否为一个对象,如果返回true,则验证成功。
其运行结果为
true
Process finished with exit code 0
可以看到,返回为true,确实为一个对象,这也解释了上述所有的情况。
------------------以下为2014年(9年前)时描述结论时的语句,这段暂不删,保留下对过去的回忆,青涩与不成熟---------------
瞅了一下,晕,最初级的一个错误,自己的两个String是同一个对象,因为如果是直接将字符串赋给一个String变量的话,jvm会首先在字符串池中看看有没有,没有的话,就创建一个字符串,放进字符串池中,然后返回引用,如果有的话,就返回池中的这个,我这样声明的两个字符串其实是一个,就是说,两次获得的锁其实是一个,这就是为什么锁获得到的原因,