一个关于使用String做锁的问题(PS:不要使用string做锁)

今天写一段程序,里面有一个锁的嵌套,在里面,我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会首先在字符串池中看看有没有,没有的话,就创建一个字符串,放进字符串池中,然后返回引用,如果有的话,就返回池中的这个,我这样声明的两个字符串其实是一个,就是说,两次获得的锁其实是一个,这就是为什么锁获得到的原因,

猜你喜欢

转载自blog.csdn.net/whodarewin2005/article/details/19771277