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

这篇文章,必须了解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线程篇(七):多线程下的缓存一致性问题)