线程安全问题

并发编程一直是业界的难题,如果你的代码程序中设计到多线程的问题,那么恭喜你!你已经走在在并发编程的道路上。

起因:
今早上班突然收到邮件,事业部在使用我们仿真系统的时候, 发现有个需求无法满足他们的业务。在设备产生数据点的时候需要依赖上一些状态值的时候出现线程安全的问题。
需求是实现自增,自减的操作。
收到邮件的时候下意识根据自己的经验提供了解决方案。

  1. 在实现类中保证共享变量的线程安全
  2. 我们在仿真系统底层屏蔽线程安全问题。

作为程序员的我, 看到这种问题有点害怕。因为我担心的是之前怎么没发现这个问题。 于是赶紧去查看源码,开始解决第二种方案。
总结是: 该事业部的同事不理解线程安全的问题, 没有去查看源码是怎么实现的。 几天前, 我根据记忆给他们的答复是每台设备的线程栈是安全的,不存在并发问题。
在 java 多线程中线程的安全问题总结如下:

  1. 局部变量: 方法内部变量始终只存在于他所在的线程(线程安全)(无状态的对象一定是线程安全的);因为方法中变量是每个线程独占的,不和其它线程共享.

  2. 多线程是否共享一个实例对象, 是否存在临界资源 (如果是不共享实例对象, 不存在临界资源的情况下,则线程安全)

  3. 多个线程使用一个实例共享类实例的成员变量,类实例本身 (单例模式(只有一个对象实例存在), 则线程不安全 )

  4. 类静态变量 (线程不安全)

局部变量

1
2
3
public int add(int count){
return ++count;// 这里也可以说无状态的对象一定是线程安全的
}

线程内部实例变量是线程安全的. 每个实例对象占用一个线程栈空间。如下代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

final ExecutorService service = Executors.newFixedThreadPool(100);
final int num = 10000;
for (int i = 0; i < num; i++) {

service.execute(new Runnable() {

long startTime = 0l;

@Override
public void run() {

startTime += 1;
if (startTime != 1) {
System.out.println("startTime ......." + startTime);
}

}
});
}


service.shutdown();
service.awaitTermination(10, TimeUnit.DAYS);

当多个线程共享同一个实例变量,访问该实例变量的成员方法就是线程不安全的操作。 当访问方法内部的变量时,此时是不存在线程安全问题的。方法内的局部变量,已经是放在每个线程自己的栈区,这个局部变量作为操作数,就只能被他自己的线程访问到。

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
final ExecutorService service = Executors.newFixedThreadPool(100);
final int num = 10000;

final Count count = new Count();

for (int i = 0; i < num; i++) {

final int a = i;
service.execute(new Runnable() {

long startTime = 0l;

@Override
public void run() {

startTime += 1;

if (startTime != 1) {
System.out.println("startTime ......." + startTime);
}

count.incr(1);
count.send(a);
}
});
}

service.shutdown();
service.awaitTermination(10, TimeUnit.DAYS);

System.out.println("count...." + count.getI());
System.out.println("result...." + count.send(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
39
40
41

public class Count {


private int i = 0;

public int getI() {
return i;
}

public void setI(int i) {
this.i = i;
}

public void incr(int i) {

this.i += i;
}


public int send(int i) {

int a = 0;

try {
Thread.sleep(randomIntValue(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
a += i;

return a;
}

public static int randomIntValue(int num) {

return new Double(Math.floor(Math.random() * num)).intValue();

}

}

所以如果要解决该同事的问题,设备实现递增递减的操作。可以在自定义数据的成员变量中使用 ThreadLocal(本地线程) 来保存每个线程的状态值 (因为多个线程只共享自己的变量,不需要其他线程的访问)。 如果是多个线程之间的通信,则考虑使用 lock 实现,或者其他一些无锁的操作。所以在底层去屏蔽线性安全问题似乎不合理,因为每台设备是无状态的操作,也会带来性能的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Created by feel on 16/8/30.
* 自定于自己的数据点生成规则
*/
public class SimplePayload extends AutomationPayloadAdapter {
/**
* @param attrs 数据点属性值
* @param time 设备运行时间
* @param mode 数据生成模式
* @return
* @throws IOException
*/
@Override
public int[] genDev2AppData(List<Attr> attrs, Long time, GenDataMode mode) throws IOException {


return new int[0];
}
}