不能忽视的 Synchronization on a non-final field
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(); }
输出结果: