线程安全之多线程a++计数减少问题(慕课网 -> 线程八大核心+Java并发底层原理精讲 掌握企业级并发问题解决方案)

你讲讲你理解的“线程安全”是什么?

《Java Concurrency In Practice》的作者Brian Goetz对“线程安全”有一个比较恰当的定义:“当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的”。

这句话的意思是:不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。
相反,如果在编程的时候,需要考虑这些线程在运行时的调度和交替(例如在get()调用到期间不能调用set()),或者需要进行额外的同步(比如使用synchronized关键字等),那么就是线程不安全的。

引发思考:java中那些类是线程安全的,那些是线程不安全的?实际业务中用了多线程不安全的类,就可能导致数据出错。

在这里插入图片描述第一种:运行结果错误

/**
 * 描述:     第一种:运行结果出错。 演示计数不准确(减少)
 */
public class MultiThreadsError implements Runnable{

    static MultiThreadsError instance = new MultiThreadsError();
    int index = 0;
    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++;
        }
    }
}

在这里插入图片描述在这里插入图片描述抓出错误代码(很有意思,学到很多)

/**
 * 描述:     第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。
 */
public class MultiThreadsError implements Runnable{

    static MultiThreadsError instance = new MultiThreadsError();
    int index = 0;

    //原子计数
    static AtomicInteger realIndex = new AtomicInteger();
    static AtomicInteger wrongCount = new AtomicInteger();
    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    //用一个marked[]数组标记是否已加过
    final boolean[] marked = new boolean[10000000];

    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("错误次数" + wrongCount.get());
    }
    @Override
    public void run() {
        marked[0] = true;
        for (int i = 0; i < 10000; i++) {
            /**第二个细节:线程执行的先后顺序执行不了,情况描述:线程1进入synchronized准备做判断的时候,可能CPU切换回去,
             index就会被线程2执行index++修改了index的值,线程1判断会导致后面的值直接被标记为true,线程2执行index++后进入
             synchronized判断发现为true,输出发生错误,但实际线程1并没有在线程2之前执行index++的写入修改*/
            //cyclicBarrier2、cyclicBarrier1可理解为两个大闸,
            //第一个闸让两个线程每一次都是同时进行index++(这里的同时不代表一定冲突),冲突是小概率事件
            //第二个闸是让两个线程都完成index++后再执行后续判断操作
            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 (){},会出现一个问题,少统计错误,index的变化导致*/
            //这种问题的情况是当两个线程,线程1先判断了marked[index]值为false,准备执行marked[index] = true的时候
            //线程2抢先判断了marked[index]值还是为false(线程1还没来得及执行marked[index] = true)
            //这是其实这已经出现问题,但却没有统计到这样的错误
            synchronized (instance){
                /**第三个细节 marked[index - 1]是因为synchronized可见性,一个线程的对值的修改,对另一个线程是可见的*/
                //情况描述:某一时刻两个线程没有发生冲0突,比如从index == 0开始,线程1的index为1,线程2的index为2,线程2先进入synchronized
                //由于synchronized的可见性,此时线程1可以感知到index值已经是2,所以后面线程1进入synchronized之后
                //判断的index值不是1,而是2,因为线程2已经已经把marked[2] = true了,所以就会输入报错。
                if (marked[index] && marked[index - 1]){
                    System.out.println("发生了错误"+index);
                    wrongCount.incrementAndGet();
                }
                marked[index] = true;
            }
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值