上一篇文章讲了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方法(具体可以自行测试下)