在变量的异步定时加载场景中,存在线程安全问题,接下来根据“单变量”和“多变量”这两种类型进行分别讨论。
一、单变量
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit;
public class SingleVariableAsyncLoadExample {
private static ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10, new ThreadFactory() {
public Thread newThread(Runnable r) { return new Thread(r); } });
private long data;
{ asyncLoad(); }
private void asyncLoad() { threadPool.scheduleAtFixedRate(new Runnable() {
@Override public void run() { data = System.currentTimeMillis() % 100; } }, 3, 3, TimeUnit.MINUTES); }
public void print() { System.out.println(data); } }
|
单变量场景又可细分为“基本类型”和“引用类型”两种子类型。
1.1、基本类型
对于基本类型,只需考虑赋值操作的线程安全问题,无需探讨其他形式的线程安全问题。在8种基本类型中,“boolean,byte,char,short,int,float”这6种基本类型变量的赋值操作是原子的,故不存在赋值操作线程安全问题;而对于“long”和“double”这两种基本类型变量的赋值操作,不一定是原子的,故存在赋值操作的线程安全问题,可通过加上volatile
修饰符加以解决。
1.2、引用类型
引用类型的赋值操作是原子的,但存在另外一种形式的线程安全问题,即“重排序导致的实例构造未完成问题”。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit;
public class SingleVariableAsyncLoadExample {
private static ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10, new ThreadFactory() {
public Thread newThread(Runnable r) { return new Thread(r); } });
private Person person;
{ asyncLoad(); }
private void asyncLoad() { threadPool.scheduleAtFixedRate(new Runnable() {
@Override public void run() { person = new Person((int) (System.currentTimeMillis() % 100), "dslztx" + (System.currentTimeMillis() % 10)); } }, 3, 3, TimeUnit.MINUTES); }
public void print() { Person localPerson = person;
System.out.println("[Name]:" + localPerson.name); System.out.println("[Age]:" + localPerson.age); } }
class Person { int age; String name;
public Person(int age, String name) { this.age = age; this.name = name; } }
|
具体存在的线程安全问题是:由于重排序,当“//4”处的localPerson变量指向新实例对象时,该新实例对象的成员变量可能仍未完成构造,故此时“//5”和“//6”打印成员变量的默认值。
可通过加上volatile
修饰符加以解决,修改后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit;
public class SingleVariableAsyncLoadExample {
private static ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10, new ThreadFactory() {
public Thread newThread(Runnable r) { return new Thread(r); } });
private volatile Person person;
{ asyncLoad(); }
private void asyncLoad() { threadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { person = new Person((int) (System.currentTimeMillis() % 100), "dslztx" + (System.currentTimeMillis() % 10)); } }, 3, 3, TimeUnit.MINUTES); }
public void print() { Person localPerson = person;
System.out.println("[Name]:" + localPerson.name); System.out.println("[Age]:" + localPerson.age); }
}
class Person { int age;
String name;
public Person(int age, String name) { this.age = age; this.name = name; } }
|
根据happens-before规则之程序顺序规则
,有“1 happens-before 2,2 happens-before 3”和“4 happens-before 5,5 happens-before 6”。当print()方法
中localPerson未指向新实例对象时,使用旧的成员变量值;否则指向新实例对象,根据happens-before规则之volatile变量规则
,有“3 hasppens-before 4”,然后再根据happens-before规则之传递性规则
,有“1 happens-before 5,2 happens-before 5,1 happens-before 6,2 happens-before 6”,不会出现上述“重排序导致的实例构造未完成问题”。
二、多变量
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit;
public class MultiVariableAsyncLoadExample {
private static ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10, new ThreadFactory() {
public Thread newThread(Runnable r) { return new Thread(r); } });
private int age;
private String name;
{ asyncLoad(); }
private void asyncLoad() { threadPool.scheduleAtFixedRate(new Runnable() {
@Override public void run() { age = (int) (System.currentTimeMillis() % 100); name = "dslztx" + (System.currentTimeMillis() % 10); } }, 3, 3, TimeUnit.MINUTES); }
public void print() { System.out.println("[Age]:" + age); System.out.println("[Name]:" + name); }
}
|
在多变量场景中,除了上述单变量导致的线程安全性问题之外,还存在另外一种形式的线程安全问题,即“多变量异步加载进度不一致问题”,比如“1处读取到新值,2处读取到旧值”。
解决方案是创建一个临时类,将相关变量作为该临时类的成员变量,最后化“多变量问题”为“单变量问题”。
改进后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit;
public class MultiVariableAsyncLoadExample {
private static ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10, new ThreadFactory() {
public Thread newThread(Runnable r) { return new Thread(r); } });
private volatile Person person;
{ asyncLoad(); }
private void asyncLoad() { threadPool.scheduleAtFixedRate(new Runnable() {
@Override public void run() { person = new Person((int) (System.currentTimeMillis() % 100), "dslztx" + (System.currentTimeMillis() % 10)); } }, 3, 3, TimeUnit.MINUTES); }
public void print() { Person localPerson = person;
System.out.println("[Name]:" + localPerson.name); System.out.println("[Age]:" + localPerson.age); }
}
class Person { int age;
String name;
public Person(int age, String name) { this.age = age; this.name = name; } }
|