在变量的异步定时加载场景中,存在线程安全问题,接下来根据“单变量”和“多变量”这两种类型进行分别讨论。
一、单变量
示例代码如下:
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、引用类型
引用类型的赋值操作是原子的,但存在另外一种形式的线程安全问题,即“重排序导致的实例构造未完成问题”。
示例代码如下:
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; //4
System.out.println("[Name]:" + localPerson.name); //5
System.out.println("[Age]:" + localPerson.age); //6
}
}
class Person {
int age;
String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
}
具体存在的线程安全问题是:由于重排序,当“//4”处的localPerson变量指向新实例对象时,该新实例对象的成员变量可能仍未完成构造,故此时“//5”和“//6”打印成员变量的默认值。
可通过加上volatile
修饰符加以解决,修改后代码如下:
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, 3, TimeUnit.MINUTES);
}
/**
* 方法会被持续调用
*/
public void print() {
Person localPerson = person; //4
System.out.println("[Name]:" + localPerson.name); //5
System.out.println("[Age]:" + localPerson.age); //6
}
}
class Person {
int age;
String name;
public Person(int age, String name) {
this.age = age; //1
this.name = name; //2
}
}
根据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”,不会出现上述“重排序导致的实例构造未完成问题”。
二、多变量
示例代码如下:
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); //1
System.out.println("[Name]:" + name); //2
}
}
在多变量场景中,除了上述单变量导致的线程安全性问题之外,还存在另外一种形式的线程安全问题,即“多变量异步加载进度不一致问题”,比如“1处读取到新值,2处读取到旧值”。
解决方案是创建一个临时类,将相关变量作为该临时类的成员变量,最后化“多变量问题”为“单变量问题”。
改进后代码如下:
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;
}
}