美文网首页Android开发经验谈
Android线程篇(七):多线程下的缓存一致性问题

Android线程篇(七):多线程下的缓存一致性问题

作者: 小五666 | 来源:发表于2018-03-25 14:33 被阅读0次

    这篇文章,必须了解Java虚拟机的内存模型和CUP的内存架构,不了解的同学速度学习前两篇。

    Java内存模型:
    Android线程篇(五):Java内存模型
    CPU内存架构:
    Android线程篇(六):CPU内存架构

    继续上篇文章的例子,稍作改动:

    Int count=0
    count=count+1
    

    如果count=count+1在单线程里面运行,这个是没有任何问题的,但是在多线程中运行就会有问题,会有什么问题呢?

    当线程执行count=count+1时会先从主内存读取count的值,然后复制一份到CPU的高速缓存中,对count进行+1操作,将count的结果写入高速缓存中,再将i的值刷新到主内存当中。

    如果有俩个线程同时执行这个代码,我们期望的结果为2,到底会出现什么情况呢?我们继续分析。

    开始时,俩个线程分别读取count的值到各自的CPU高速缓存当中,线程1和线程2对count进行+1操作,线程1将count的结果写入高速缓存中,再将i的值刷新到主内存当中,此时线程2高速缓存中,count的值还是0,进行加1操作之后,count的值为1,然后线程2把count的值写入内存,这个时候count的值还为1。

    最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。

    在多线程编程的时候,如果一个变量在多个CUP中都有缓存,就可能会出现缓存不一致性问题。

    问题清楚了,我们如何来解决这个问题呢?

    来先上个例子,至于为什么不用Int请看上篇文章:

    文章链接:
    Java线程并发小例子的思考,寻求大佬答疑解惑

        public Integer count = 0;
        public int TestVolatile(){
            final CountDownLatch countDownLatch = new CountDownLatch(1000);
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                        }
    
                        count++;
                        countDownLatch.countDown();
                    }
                }).start();
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("<<<<<"+count);
            return count;
        }
    

    Log:

    03-18 03:00:16.098 5569-5569/com.example.myapplication I/System.out: <<<<<863
    03-18 03:01:55.414 5569-5569/com.example.myapplication I/System.out: <<<<<1000
    03-18 03:01:58.210 5569-5569/com.example.myapplication I/System.out: <<<<<976
    03-18 03:02:00.426 5569-5569/com.example.myapplication I/System.out: <<<<<925
    

    我们期望count结果等于1000,结果看log,都是小于1000的,那么我们如何能让结果等于我们期望的1000呢:
    第一种:
    采用synchronized:

    public Integer count = 0;
        public Integer TestVolatile() {
            final CountDownLatch countDownLatch = new CountDownLatch(1000);
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                        }
    
                        increase();
                        countDownLatch.countDown();
                    }
                }).start();
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("<<<<<" + count);
            return count;
        }
        public synchronized void increase() {
            count++;
        }
    

    Log:

    03-25 14:20:02.426 15465-15465/? I/System.out: <<<<<1000
    03-25 14:20:09.868 15465-15465/com.example.myapplication I/System.out: <<<<<1000
    03-25 14:20:13.214 15465-15465/com.example.myapplication I/System.out: <<<<<1000
    

    第二种:采用Lock:

        public Integer count = 0;
        Lock lock = new ReentrantLock();
        public Integer TestVolatile() {
            final CountDownLatch countDownLatch = new CountDownLatch(1000);
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                        }
    
                        increase();
                        countDownLatch.countDown();
                    }
                }).start();
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("<<<<<" + count);
            return count;
        }
        public void increase() {
            lock.lock();
            try {
                count++;
            } finally{
                lock.unlock();
            }
    
        }
    

    这里就不贴Log了,有兴趣的朋友自己试。。。

    第三种:采用AtomicInteger:

    public AtomicInteger count = new AtomicInteger();
        public AtomicInteger TestVolatile() {
            final CountDownLatch countDownLatch = new CountDownLatch(1000);
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                        }
    
                        increase();
                        countDownLatch.countDown();
                    }
                }).start();
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("<<<<<" + count);
            return count;
        }
        public void increase() {
            count.getAndIncrement();
    
        }
    

    至于这几种方法都有什么优劣,他们的原理都有什么,后面的文章我们继续讲解。

    相关文章

      网友评论

        本文标题:Android线程篇(七):多线程下的缓存一致性问题

        本文链接:https://www.haomeiwen.com/subject/lnvtfftx.html