Java高并发编程之synchronized关键字(二)

上一篇文章讲了synchronized的部分关键要点,详见:Java高并发编程之synchronized关键字(一)
本篇文章接着讲synchronized的其他关键点。
在使用synchronized关键字的时候,不要以字符串常量作为锁定对象。看下面的例子:

public class T012 {
    public String s1 = "hello";
    public String s2 = "hello";

    public void m1() {
        synchronized (s1) {
            System.out.println("m1 start...");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("m1 end.");
        }
    }

    public void m2() {
        synchronized (s2) {
            System.out.println("m2 start...");
            System.out.println("m2 end.");
        }
    }

    public static void main(String[] args) {
        T012 t012 = new T012();
        new Thread(t012::m1, "Thread_1").start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(t012::m2, "Thread_2").start();
    }
}

thread_1执行m1方法,thread_2执行m2方法,然而m2方法却要等到m1执行结束才开始,这说明,m1和m2其实锁定的是同一个对象。这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串“hello”,但是因为你读不到源码,所以你在自己的代码中也锁定了“hello”,这时候就有可能发生非常诡异的死锁阻塞,因为你的程序和你的类库不经意间使用了同一把锁。
锁定某个对象o,如果o的属性发生改变,不影响锁的使用。但是如果o变成了另一个对象,则锁定的对象发生改变,应该避免 将锁定对象的应用变成另外的对象。

public class T013 {
    Object o = new Object();
    void m() {
        synchronized (o) {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        T013 t013 = new T013();
        new Thread(t013::m, "thread_1").start();

        try {
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        Thread t2 = new Thread(t013::m, "thread_2");
        //锁对象发生改变
        t013.o = new Object();
        t2.start();
    }
}

上面的代码中,如果对象o所引用的对象不发生改变,t2线程是不会执行的,然而运行程序可发现t2线程也运行了,这就证明,synchronized锁定的是堆内存中的对象,而不是对象的应用,所以要避免改变锁定的对象。
如何对synchronized进行优化呢?同步代码块中的语句越少越好!
观察下面程序:

public class T014 {
     int count = 0;
     synchronized void m1() {
         try {
             Thread.sleep(1000);
         } catch (Exception e) {
             e.printStackTrace();
         }
         
         count++;
         try {
             Thread.sleep(2000);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
    
     void m2() {
         try {
             Thread.sleep(1000);
         } catch (Exception e) {
             e.printStackTrace();
         }
         //业务逻辑中只有count++需要sync,这时不应该给整个方法上锁
         //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
         synchronized (this) {
             count++;
         }
         try {
             Thread.sleep(2000);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
}

上述代码中,m2方法的效率,要远高于m1方法(具体可以自行测试下)

你可能感兴趣的:(Java开发)