图解java多线程-Single_Threaded_Execution_模式

正文

BadCount1

我来举一个例子,例如下面的代码
这里我定义了一个 Count 的接口,用来计数,方法有增加计数 add,减少计数 remove 以及获取当前计数 getCount
我根据这个接口实现了一个糟糕的计数类 BadCount1,该类可以用来记录某个房间到底进入了多少人,当有人进入则调用 add(),出去调用 remove()
我写了个测试类 CountThread 来测试这个计数类有多糟糕,它是一个线程类,线程中的方法是进入房间然后出去100次,测试方法 check 表示启动十个测试类,每个进出100次,当所有线程执行结束,打印房间中的人数,很明显理论上应该是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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public interface Count {
void add();

void remove();

int getCount();

}

public class BadCount1 implements Count {

private int count = 0;

@Override
public void add() {
count++;
}

@Override
public void remove() {
count--;
}

@Override
public int getCount() {
return count;
}


/**
* 3
*/
public static void main(String[] args) {
BadCount1 badCount = new BadCount1();
CountThread.check(badCount);
}
}

public class CountThread<T extends Count> implements Runnable {
private T t;

public CountThread(T t) {
this.t = t;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
t.add();
t.remove();
}
}

public static <K extends Count> void check(K count) {
List<Thread> list = IntStream.range(0, 10).mapToObj(i -> new Thread(new CountThread<>(count))).collect(Collectors.toList());
list.forEach(Thread::start);
for (Thread thread : list) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(count.getCount());
}
}

观察上面的代码,我们主要看以下线程中的方法

1
2
3
4
for (int i = 0; i < 100; i++) {
t.add();
t.remove();
}

这个方法中调用的 add 和 remove 针对 count 变量做了 ++ 和 - - 操作,有可能发生如下操作

WorseCount1

上面 BadCount1 可能需要多执行几次才会看到效果,为了更快的出错,我写了个更糟糕的计数类 WorseCount1

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
public class WorseCount1 implements Count {
private int count;

@Override
public void add() {
int tmp = count + 1;
Thread.yield();
count = tmp;
}

@Override
public void remove() {
int tmp = count - 1;
Thread.yield();
count = tmp;
}

@Override
public int getCount() {
return count;
}


/**
* -7
*
*/
public static void main(String[] args) {
WorseCount1 badCount = new WorseCount1();
CountThread.check(badCount);
}
}

那么怎么将这个糟糕的计数类变成安全可用的了,问题在于首先这个计数类是一个公用的类,所有的线程都公用这一个计数类,当其中一个线程将值修改之后,其他线程并不知道,导致所有线程对于 count 字段的修改并没有完全作用在 count 字段上,然而线程本来就是并发的,当他们运行到需要修改 count 的时候,需要让他们一个一个作用在 count,就像过独木桥一样,所有线程都要一个一个走在桥上.

SafeCount

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
55
public class SafeCount implements Count {
private volatile int count = 0;

@Override
public synchronized void add() {
count++;
}

@Override
public synchronized void remove() {
count--;
}

@Override
public synchronized int getCount() {
return count;
}


/**
* 0
*/
public static void main(String[] args) {
SafeCount badCount = new SafeCount();
CountThread.check(badCount);
}
}

public class SafeCount1 implements Count {
private AtomicInteger count = new AtomicInteger(0);

@Override
public void add() {
count.incrementAndGet();
}

@Override
public void remove() {
count.decrementAndGet();
}

@Override
public int getCount() {
return count.get();
}


/**
* 0
*/
public static void main(String[] args) {
SafeCount1 badCount = new SafeCount1();
CountThread.check(badCount);
}
}

这里千万不要认为是执行了 add() 又执行 remove() 所以有线程问题,就算线程里面只执行 add() 操作同样也会有线程问题,比如增加到100的时候突然切换线程,另外一根线程100->101,然后又切换回来,同样为100->101,所以其中的一次加就被省略了,因为切换的那次加法没有作用在切换后的线程中.

Cream Bing wechat
subscribe to my blog by scanning my public wechat account