Java并发之 CAS + Thread.join / CountDownLatch 方式实现线程安全

来源:互联网 发布:向日葵软件如何视频 编辑:程序博客网 时间:2024/06/04 22:36

一、问题描述

在多线程开发中,由并发引起的问题很不容易发觉,这里分别实现了线程安全和非线程安全的两种计数器。可以明显的看出多线程并发引发的数据丢失问题。

二、问题分析

  1. 这里非线程安全的计数器的起因是计数器中的count++;操作是非原子操作。
  2. 为解决count++;非原子操作问题,这里模拟了硬件级解决方案CAS(Compare And Swap,比较并交换),是一种乐观锁方案。

    CAS有3个操作数,内存位置V,旧的预期值A和新值B。CAS的意思为:我认为V的值应该是A,如果是,那么将其赋值为B,若不是,则不修改,并告诉我应该为多少。它抱着成功的希望进行更新,并且如果另一个线程在上次检查后更新了该变量,它能够发现错误。

  3. CAS模拟代码:

    public synchronized int increaseCountWithCas(int exceptValue, int newValue){        int oldValue = count;        if(oldValue == exceptValue){            count = newValue;        }        return oldValue;    }
  4. 在模拟多线程并发时,利用两种方式实现线程:
    1. 扩展Thread类
    2. 使用runnable

三、代码结构

四、counter代码

  1. 非线程安全计数器

    package counter;/*** * 非线程线程安全的计数器 * @author zq * */public class UnsafeCounter {    private ValueWithoutCas valueWithoutCas = new ValueWithoutCas();    public int getValue(){        return valueWithoutCas.getCount();    }    public int increase(){        valueWithoutCas.increaseCount();        return valueWithoutCas.getCount();    }}

    其中,ValueWithoutCas代码如下

    package counter;/*** * 非线程安全计数器使用的计数类 * @author zq * */public class ValueWithoutCas {    private int count;    //不安全的计数值增加    public void increaseCount(){        count++;    }    public int getCount(){        return count;    }}
  2. 线程安全计数器

    package counter;/*** * 基于CAS实现的非阻塞线程安全计数器 * @author zq * */public class SafeCounter {    private ValueWithCas valueWithCas = new ValueWithCas();    public int getValue(){        return valueWithCas.getCount();    }    public int increase(){        int v;        do{            v = valueWithCas.getCount();        }while(v != valueWithCas.increaseCountWithCas(v, v + 1));        return v + 1;    }}

    其中,valueWithCas 代码如下

    package counter;/*** * 线程安全计数器使用的计数类 * @author zq * */public class ValueWithCas {    private int count;    //模拟CAS实现计数值增加    public synchronized int increaseCountWithCas(int exceptValue, int newValue){        int oldValue = count;        if(oldValue == exceptValue){            count = newValue;        }        return oldValue;    }    public synchronized int getCount(){        return count;    }}

五、runnable方式模拟线程

  1. 操作非线程安全计数器

    package runnablePackage;import counter.UnsafeCounter;/*** * 模拟线程:操作非线程安全计数器 * @author zq */public class RunnableWithoutCAS implements Runnable {    private static UnsafeCounter unsafeCounter = new UnsafeCounter();    @Override    public void run() {        unsafeCounter.increase();    }    public static UnsafeCounter getUnsafeCounter(){        return unsafeCounter;    }}
  2. 操作线程安全计数器

    package runnablePackage;import counter.SafeCounter;/*** * 模拟线程:操作线程安全计数器 * @author zq */public class RunnableWithCAS implements Runnable {    private static SafeCounter safeCounter = new SafeCounter();    @Override    public void run() {        safeCounter.increase();    }    public static SafeCounter getSafeCounter(){        return safeCounter;    }}

六、thread方式模拟线程

  1. 操作非线程安全计数器

    package threadPackage;import counter.UnsafeCounter;/*** * 模拟线程:操作非线程安全计数器 * @author zq */public class ThreadIncWithUnsafe extends Thread {    UnsafeCounter unsafeCounter;    public ThreadIncWithUnsafe(UnsafeCounter unsafeConunter){        super();        this.unsafeCounter = unsafeConunter;    }    public void run(){        unsafeCounter.increase();    }}
  2. 操作线程安全计数器

    package threadPackage;import counter.SafeCounter;/*** * 模拟线程:操作线程安全计数器 * @author zq */public class ThreadIncWithSafe extends Thread{    SafeCounter safeCounter;    public ThreadIncWithSafe(SafeCounter safeCounter){        super();        this.safeCounter = safeCounter;    }    public void run(){        safeCounter.increase();    }}

七、测试代码

  1. 以runnable和thread方式分别模拟10000个线程操作线程安全计数器和非线程安全计数器(计数器初始值为0)。正常情况下,10000个线程操作完成后,计数器值应该为10000。

    这里使用Thread.currentThread().join(10)方法实现main线程等待它启动的所有子进程完成后输出计数器结果。

        package cas;    import counter.SafeCounter;    import counter.UnsafeCounter;    import runnablePackage.RunnableWithCAS;    import runnablePackage.RunnableWithoutCAS;    import threadPackage.ThreadIncWithSafe;    import threadPackage.ThreadIncWithUnsafe;    public class CasTest {        public static void main(String[] args) {            doWithThread();            System.out.println();            doWithRunnable();        }        public static void doWithThread(){            //操作非线程安全计数器            UnsafeCounter unsafeCounter = new UnsafeCounter();            for(int i = 1; i <= 10000; i++){                Thread t = new ThreadIncWithUnsafe(unsafeCounter);                t.start();            }            waitSubThreadComplete();            System.out.println("doWithThread, unsafe result: " + unsafeCounter.getValue());            //操作线程安全计数器            SafeCounter safeCounter = new SafeCounter();            for(int j = 1; j <= 10000; j++){                Thread T = new ThreadIncWithSafe(safeCounter);                T.start();            }            waitSubThreadComplete();            System.out.println("doWithThread, safe result: " + safeCounter.getValue());        }        public static void doWithRunnable(){            //操作非线程安全计数器            for(int i = 1; i <= 10000; i++){                Thread t = new Thread(new RunnableWithoutCAS());                t.start();            }            waitSubThreadComplete();//使main线程等待它启动的所有子进程完成后,打印计数器结果            System.out.println("doWithRunnable,unsafe result: " + RunnableWithoutCAS.getUnsafeCounter().getValue());            //操作线程安全计数器            for(int i = 1; i <= 10000; i++){                Thread t = new Thread(new RunnableWithCAS());                t.start();            }            waitSubThreadComplete();            System.out.println("doWithRunnable,safe result:   " + RunnableWithCAS.getSafeCounter().getValue());        }        /***         * 使main线程等待它启动的所有子进程完成         */        public static void waitSubThreadComplete(){            try {                Thread.currentThread().join(10);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }
  2. 在上述测试代码中,doWithRunnable函数也可以使用CountDownLatch保证main线程等待它启动的所有子进程完成后再输出计数器的值。代码如下:

    /**     * 使用CountDownLatch保证所有子进程完成后再输出结果     * */    public static void doWithRunnable(){        //操作非线程安全计数器        final CountDownLatch unsafeEndGate = new CountDownLatch(10000);        for(int i = 1; i <= 10000; i++){            Thread t = new Thread(){                public void run(){                    try{                        try{                            new RunnableWithoutCAS().run();                        }finally{                            unsafeEndGate.countDown();                        }                    }catch(Exception e){                    }                }            };            t.start();        }//      waitSubThreadComplete();//使main线程等待它启动的所有子进程完成后,打印计数器结果        try {            unsafeEndGate.await();        } catch (InterruptedException e1) {            e1.printStackTrace();        }        System.out.println("doWithRunnable,unsafe result: " + RunnableWithoutCAS.getUnsafeCounter().getValue());        //操作线程安全计数器        final CountDownLatch safeEndGate = new CountDownLatch(10000);        for(int i = 1; i <= 10000; i++){            Thread t = new Thread(){                public void run(){                    try{                        try{                            new RunnableWithCAS().run();                        }finally{                            safeEndGate.countDown();                        }                    }catch(Exception e){                    }                }            };            t.start();        }//      waitSubThreadComplete();        try {            safeEndGate.await();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("doWithRunnable,safe result:   " + RunnableWithCAS.getSafeCounter().getValue());    }

八、运行结果

doWithThread, unsafe result: 9998doWithThread, safe result: 10000doWithRunnable,unsafe result: 9997doWithRunnable,safe result:   10000
0 0