0%

变量的异步定时加载之线程安全

在变量的异步定时加载场景中,存在线程安全问题,接下来根据“单变量”和“多变量”这两种类型进行分别讨论。

一、单变量

示例代码如下:

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; //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修饰符加以解决,修改后代码如下:

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, 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”,不会出现上述“重排序导致的实例构造未完成问题”。

二、多变量

示例代码如下:

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); //1
System.out.println("[Name]:" + name); //2
}

}

在多变量场景中,除了上述单变量导致的线程安全性问题之外,还存在另外一种形式的线程安全问题,即“多变量异步加载进度不一致问题”,比如“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;
}
}
您的支持将鼓励我继续分享!