Synchronization on a non-final field
今天在做一个多线程试验的时候,对一个变量使用 synchronized ,出现了这样的提示 “Synchronization on a non-final field XXX”,查看详细文档可以看到下面这段话:
Reports synchronized statements where the lock expression is a reference to a non-final field. Such statements are unlikely to have useful semantics, as different threads may be locking on different objects even when operating on the same object.
百度翻译:报告同步语句,其中锁表达式是对非最终字段的引用。这样的语句不太可能具有有用的语义,因为即使在同一对象上操作,不同的线程也可能锁定不同的对象。
一、解释
官方注释的解释大概意思是我们对一个变化的对象加锁,如果这个变量的引用发生了改变,不同的线程可能锁定不同的对象,都会成功获得各自的锁。
二、尝试
为了进一步弄明白这段话的意义,进行了一个小试验,写了如下代码。
private static String flag = "";
public static class Task implements Runnable {
private final int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
synchronized (flag) {
System.out.println(String.format("Thread-%d start.", this.id));
flag = "123";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("Thread-%d end.", this.id));
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Task(1)).start();
Thread.sleep(100);
new Thread(new Task(2)).start();
}
我的目的是想让两个线程分别有序执行,第一个线程结束后第二个线程再开始,于是锁定了一个 flag
,这是一个变量。
打印如下:
可以看到两个线程几乎同时开始执行,也就是文档中的那句话,两个线程锁定到了不同的对象,所以与我们预想的完全不同。
三、修改
我采用了一个数组的方式来存放我们要修改的对象,且用 final
修饰 ,这样可以有效避免被锁对象的引用发送转变。其实也可以用其他对象包装。
private static final String[] flag = {""};
public static class Task implements Runnable {
private final int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
synchronized (flag) {
System.out.println(String.format("Thread-%d start.", this.id));
flag[0] = "123";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("Thread-%d end.", this.id));
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Task(1)).start();
Thread.sleep(100);
new Thread(new Task(2)).start();
}
输出结果: