栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

线程安全之多线程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++) {
            
            //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();
            
            //这种问题的情况是当两个线程,线程1先判断了marked[index]值为false,准备执行marked[index] = true的时候
            //线程2抢先判断了marked[index]值还是为false(线程1还没来得及执行marked[index] = true)
            //这是其实这已经出现问题,但却没有统计到这样的错误
            synchronized (instance){
                
                //情况描述:某一时刻两个线程没有发生冲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;
            }
        }
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/390053.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号