Java多线程 a++线程问题 找出减少的地方

AtomicInteger原子类来统计相加减少的次数

如下的代码, 使用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. 所以就导致了发生错误

synchronized 加锁进行统计

按照上面的分析, 把代码修改如下, 把可能出现线程切换的地方, 加上锁 .

但此时执行代码发现, 执行的结果明明是2万 , 没有出错的 , 但是统计发生错误却是479条.

假设两个线程都执行完上面的46 48行代码, 同时到50行代码去拿锁, 假设此时发生了冲突. 此时index为0 , 相加后变成了1 , 第一个线程把布尔数组的下标为1 的设置成了true, 但可能在线程2执行到51行代码的时候, 就把线程切换成了线程1, 此时线程1执行++操作, index就变成了2. 那么此时原本是想比较的是index为1 的时候, index 却变成了2.

CyclicBarrier 线程等待的工具类

这个时候, 就需要引入一个工具类, 这个工具类的作用是让线程在需要等待 的地方进行等待. 不想让线程等待的地方, 可以进行统一的执行.

这个工具类就是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;
            }
        }
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值