如下的代码, 使用AtomicInteger
原子类来统计相加减少的次数. 和发生错误的次数.
并且用布尔数组, 如果某个值, 相加了, 那么就设置成true.(boolean数组 , 里面的元素, 默认为false)
public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError();
int index = 0;
//真正加的次数
static AtomicInteger realIndex = new AtomicInteger();
//发生错误的次数 AtomicInteger的作用是把i++的操作, 三步合为一步, 原理为 cas
static AtomicInteger wrongIndex = new AtomicInteger();
//存储当前值的数组
final boolean[] marked = new boolean[1000000000];
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.get());
System.out.println("发生错误的次数"+ wrongIndex.get());
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
//自增
realIndex.incrementAndGet();
if (marked[index]) {
//如果该值, 已经被加过了, 那么就会进入此代码块
//打印出线程错误的下标
System.out.println("发生了错误" + index);
//相加错误的数量
wrongIndex.incrementAndGet();
}
//把当前加完的值 ,设置为true
marked[index] = true;
}
}
}
之所以用如下的代码,进行出现错误的时候的打印, 是因为上一篇文章中, 已经提到, 在发生线程不安全问题的时候, 变量i的值, 还没来得及赋值给当前线程, 就去执行线程2的代码了. 这样就会导致i的值变少, 但是会布尔数组的索引是相同的, if的判断就是为true的.
https://javaweixin6.blog.csdn.net/article/details/108327742
执行程序, 可以看到执行结果与发生错误的次数相加并不是2万,
发生上面错误的原因是, 可能在执行57行代码的时候, 也会出现线程的不安全问题 . 一个线程执行完了i++操作, 执行到49行代码为false , 原本打算执行57行代码, 把标记位设置成true, 但此时发生了线程的切换. 第二个线程判断49行代码的时候的, 原本应该是true的, 但是却是false. 所以就导致了发生错误
按照上面的分析, 把代码修改如下, 把可能出现线程切换的地方, 加上锁 .
但此时执行代码发现, 执行的结果明明是2万 , 没有出错的 , 但是统计发生错误却是479条.
假设两个线程都执行完上面的46 48行代码, 同时到50行代码去拿锁, 假设此时发生了冲突. 此时index为0 , 相加后变成了1 , 第一个线程把布尔数组的下标为1 的设置成了true, 但可能在线程2执行到51行代码的时候, 就把线程切换成了线程1, 此时线程1执行++操作, index就变成了2. 那么此时原本是想比较的是index为1 的时候, index 却变成了2.
这个时候, 就需要引入一个工具类, 这个工具类的作用是让线程在需要等待 的地方进行等待. 不想让线程等待的地方, 可以进行统一的执行.
这个工具类就是CyclicBarrier, 可以根据我们的需要, 在某个地方进行等待, 直到等待的资源都就绪了,再执行代码 .
引入CyclicBarrier 工具类后, 改造如下 . 在循环一开始和执行++操作的时候, 进行等待,
package com.thread.background;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 类名称:MultiThreadsError
* 类描述: 第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。
*
* @author: https://javaweixin6.blog.csdn.net/
* 创建时间:2020/8/31 19:18
* Version 1.0
*/
public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError();
int index = 0;
//真正加的次数
static AtomicInteger realIndex = new AtomicInteger();
//发生错误的次数 AtomicInteger的作用是把i++的操作, 三步合为一步, 原理为 cas
static AtomicInteger wrongIndex = new AtomicInteger();
//CyclicBarrier构造方法, 传递的参数代表需要等待几个线程
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
//存储当前值的数组
final boolean[] marked = new boolean[1000000000];
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.get());
System.out.println("发生错误的次数"+ wrongIndex.get());
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
//等待的重置
cyclicBarrier2.reset();
//等待的两个线程如果都到齐了, 那么就放行.
cyclicBarrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
index++;
//两个线程都去执行++操作的时候, 才去放行
try {
//等待的重置
cyclicBarrier1.reset();
cyclicBarrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//自增
realIndex.incrementAndGet();
synchronized (instance) {
if (marked[index]) {
//如果该值, 已经被加过了, 那么就会进入此代码块
//打印出线程错误的下标
System.out.println("发生了错误" + index);
//相加错误的数量
wrongIndex.incrementAndGet();
}
//把当前加完的值 ,设置为true
marked[index] = true;
}
}
}
}
可以看到统计的 发生错误的统计还是出错了. 但是可以发现的规律是统计的发生的错误, 都是偶数.
主要是因为synchronized的可见性, 让两个线程的结果可以互相的通知.
需要把上面的代码, 修改一处. 不仅仅是判断当前索引, 还得判断上一个索引是否为true.
通过debug断点可以看到. 布尔数组的规律基本上是前面一个是true, 后面一个是false.
把布尔数组的第一个元素设置成true, 用于防止第一次就发生了错误
此时控制台打印如下 , 可以看到统计没有出错了.
完整的代码如下:
package com.thread.background;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 类名称:MultiThreadsError
* 类描述: 第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。
*
* @author: https://javaweixin6.blog.csdn.net/
* 创建时间:2020/8/31 19:18
* Version 1.0
*/
public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError();
int index = 0;
//真正加的次数
static AtomicInteger realIndex = new AtomicInteger();
//发生错误的次数 AtomicInteger的作用是把i++的操作, 三步合为一步, 原理为 cas
static AtomicInteger wrongIndex = new AtomicInteger();
//CyclicBarrier构造方法, 传递的参数代表需要等待几个线程
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
//存储当前值的数组
final boolean[] marked = new boolean[1000000000];
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.get());
System.out.println("发生错误的次数"+ wrongIndex.get());
}
@Override
public void run() {
//把布尔数组的第一个元素设置成true, 用于防止第一次就发生了错误
marked[0] = true;
for (int i = 0; i < 10000; i++) {
try {
//等待的重置
cyclicBarrier2.reset();
//等待的两个线程如果都到齐了, 那么就放行.
cyclicBarrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
index++;
//两个线程都去执行++操作的时候, 才去放行
try {
//等待的重置
cyclicBarrier1.reset();
cyclicBarrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//自增
realIndex.incrementAndGet();
synchronized (instance) {
if (marked[index] && marked[index-1]) {
//如果该值, 已经被加过了, 那么就会进入此代码块
//打印出线程错误的下标
System.out.println("发生了错误" + index);
//相加错误的数量
wrongIndex.incrementAndGet();
}
//把当前加完的值 ,设置为true
marked[index] = true;
}
}
}
}