a++多线程下出现消失的请求现象
a++多线程下出现消失的请求现象
a++多线程下出现消失的请求现象是一个常见的多线程运行结果错误的例子。
我们先来看一下a++代码执行图:
按照我们想让它执行的顺序执行,结果应该是3。但在多线程下,结果就不一定如此。
代码执行到i+1时,+1后的数值还没赋给i,如果在这个时候发生线程切换,那么此时i还是1。
那么就会发生运行结果和预期不一样的错误。
我们用代码模拟一下
public class MultiThreadErrorExample implements Runnable{
static MultiThreadErrorExample instance = new MultiThreadErrorExample();
int index;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("表面结果是" + instance.index);
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
}
}
}
确实发生预期的错误,但这里我们不能定位出发生错误的位置(index),所以我们要对例子升级!!!大家可以先思考一下,如何实现这个功能?
进阶
先上代码,后面再做解释:
public class MultiThreadErrorAdvanced implements Runnable {
static MultiThreadErrorAdvanced instance = new MultiThreadErrorAdvanced();
int index = 0;
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongIndex = new AtomicInteger();
static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
final boolean[] marked = new boolean[100000];
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("表面结果是" + instance.index);
System.out.println("成功次数" + realIndex);
System.out.println("失败次数" + wrongIndex);
}
@Override
public void run() {
marked[0] = true;
for (int i = 0; i < 10000; i++) {
index++;
realIndex.incrementAndGet();
synchronized (instance) {
if (marked[index]) {
System.out.println("发生错误" + index);
wrongIndex.incrementAndGet();
}
marked[index] = true;
}
}
}
}
思路:我们使用一个布尔值数组来存取每个index的是否完成++操作的标志。如果
marked[index]
为ture,
说明该index已完成过一次操作,那么当前操作就是错误的了。
这里我们引用了原子类来做统计,原因是原子类的操作是原子性的,不会因为线程的切换,发生结果的不同。
理所当然我们对进行错误的判断的代码块用synchronized
修饰。防止发生线程切换时,依然可以进入该代码块,发生错误。
但执行结果依然不让人满意,大家可以思考一下为什么??
解答:
- 一种情况:当线程1 index=0,然后index++。此时index=1,如果此时线程2执行了index++,那么index =2;
此时,线程1获得锁进入同步代码块。线程2没有锁,于是等待线程1释放锁。此时marked[2]未赋值,为false,于是执行了marked[index] = true;
;
线程1完事了,轮到线程2,尴尬了。此时,marked[2]被线程1赋值为true了,于是打印了报错信息。 - 另一种: 如果线程1、线程2刚好在
index++
发生争夺,那么可能发生线程1、2的index都为1的情况。于是就会影响到下面的判断。
解决方法:
我们应该给index++加上栅栏,使得两个线程必须都完成index++才可以往下执行,这里我们使用CyclicBarrier
这个工具类,可以发生上述的两种情况。
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
public class MultiThreadErrorAdvanced implements Runnable {
static MultiThreadErrorAdvanced instance = new MultiThreadErrorAdvanced();
int index = 0;
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongIndex = new AtomicInteger();
static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
final boolean[] marked = new boolean[100000];
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("表面结果是" + instance.index);
System.out.println("成功次数" + realIndex);
System.out.println("失败次数" + wrongIndex);
}
@Override
public void run() {
marked[0] = true;
for (int i = 0; i < 10000; i++) {
try {
cyclicBarrier2.reset();
cyclicBarrier1.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
index++;
try {
cyclicBarrier1.reset();
cyclicBarrier2.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
realIndex.incrementAndGet();
synchronized (instance) {
if (marked[index] && marked[index - 1]) {
System.out.println("发生错误" + index);
wrongIndex.incrementAndGet();
}
marked[index] = true;
}
}
}
}
重点来了!!!是这行代码marked[index - 1]
,并将marked[0]赋值为true
加完栅栏后,index=2,对于线程1、2都一样。所以可能发生上述情况的第一种。所以当前一位也为true时,才是正在发生错误。排除因为栅栏导致index重复的情况。
}
重点来了!!!是这行代码marked[index - 1]
,并将marked[0]赋值为true
加完栅栏后,index=2,对于线程1、2都一样。所以可能发生上述情况的第一种。所以当前一位也为true时,才是正在发生错误。排除因为栅栏导致index重复的情况。
上一篇: ForkJoinPool的使用
下一篇: JAVA 面试 - 1
推荐阅读