美文网首页
线程安全与非线程安全

线程安全与非线程安全

作者: 小和尚恋红尘 | 来源:发表于2018-08-31 17:19 被阅读0次
    线程安全

    多线程访问时,对数据进行加锁保护,防止数据出现不一致或者数据污染情况。即:当一个线程要访问某类中的数据时,会对其加锁保护,只有当此线程访问完成后,其它线程才能继续访问。

    非线程安全

    多线程访问数据时,不对数据进行加锁保护,出现数据不一致或者数据污染情况。非线程安全问题我们一般通过synchronized关键字加锁控制。

    关于线程安全和非线程安全问题,我们应该都不陌生。
    例如:

    请问:ArrayListVector有什么区别?HashMapHashTable有什么区别?StringBuilderStringBuffer有什么区别?
    回答:ArrayList是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuffer是线程安全的;
    在问:那么什么是线程安全?和非线程安全的区别是什么?会在什么情况下使用?
    回答:.....

    这样一套组合拳下来,半条命都没有了,都开始有点怀疑人生了......下面对这些问题进行具体的讲解。

    来看看下面的这个非线程安全现象,我们以ArrayListVector为例
    新建一个非线程安全的ArrayList集合,在建立100个线程,每个线程中都对ArrayList添加100条数据,那么最终集合中有多少数据呢?

    public class MultiThreadListClass {
        public static void main(String[] args) {
            List<Object> addList = new ArrayList<>();
            int threadCount = 100;
            int threadCount = 100;
            for (int m = 0; m < 10; m++) {
                List<Object> addList = new ArrayList<>();
                CountDownLatch mCountDownLatch = new CountDownLatch(threadCount);
                AddThread addThread = new MultiThreadListClass().new AddThread(mCountDownLatch, addList);
    
                for (int i = 0; i < threadCount; i++) {
                    Thread thread = new Thread(addThread);
                    thread.start();
                }
    
                try {
                    mCountDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("addListSize:"+ addList.size());
            }
        }
    
        private class AddThread implements Runnable {
            private CountDownLatch countDownLatch;
            private List<Object> list;
    
            AddThread(CountDownLatch countDownLatch, List<Object> list) {
                this.countDownLatch = countDownLatch;
                this.list = list;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    list.add(new Object());
                }
                countDownLatch.countDown();//完成一个子线程
            }
        }
    }
    

    运行输出结果为:

    addListSize:9963
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:9888
    addListSize:9865
    addListSize:10000
    addListSize:10000
    Exception in thread "Thread-800" java.lang.ArrayIndexOutOfBoundsException: 14
        at java.util.ArrayList.add(ArrayList.java:459)
        at com.example.MultiThreadListClass$AddThread.run(MultiThreadListClass.java:93)
        at java.lang.Thread.run(Thread.java:745)
    Exception in thread "Thread-803" java.lang.ArrayIndexOutOfBoundsException: 1400
        at java.util.ArrayList.add(ArrayList.java:459)
        at com.example.MultiThreadListClass$AddThread.run(MultiThreadListClass.java:93)
        at java.lang.Thread.run(Thread.java:745)
    

    从上面的输出结果来看,并不是每次都是10000,有时会造成异常。这就是非线程安全引发的问题了。我们在用Vector集合来看看输出,更改代码如下:

    List<Object> addList = new ArrayList<>();
    替换为:
    List<Object> addList = new Vector<>();
    

    运行输出结果为:

    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    addListSize:10000
    

    从结果看,每次输出的都是10000,可以在多运行几次进行测试。可见数据一致,也就是线程安全的。在多线程操作Vector时,不会造成任何问题。我们在用LinkedList测试下,直接看结果:

    addListSize:9966
    addListSize:9965
    addListSize:10000
    addListSize:10000
    addListSize:9943
    addListSize:10000
    addListSize:9963
    addListSize:9851
    addListSize:9900
    addListSize:9908
    

    从结果看,LinkedList也是非线程安全的。我们在用Collections.synchronizedListCopyOnWriteArrayList测试下,更改代码为:

    List<Object> addList = new ArrayList<>();
    替换为:
    List<Object> addList = new CopyOnWriteArrayList<>();
    或者为:
    List<Object> addList = Collections.synchronizedList(new ArrayList<>());
    

    运行结果都为10000,也就是这两者是线程安全的。我们来看这两者在读写上的差别,看下面代码:

    public class CallableUtils {
        private int num;
        private int threadSize;
    
        CallableUtils(int num, int threadSize){
            this.num = num;
            this.threadSize = threadSize;
        }
    
        public void add() {
            List<Integer> list1 = Collections.synchronizedList(new ArrayList<Integer>());
            List<Integer> list2 = new CopyOnWriteArrayList<>();
            int addCowTotalTime = 0;
            int addCsTotalTime = 0;
            ExecutorService executor = Executors.newFixedThreadPool(threadSize);
            CountDownLatch countDownLatch = new CountDownLatch(threadSize);
            try {
                for (int i = 0; i < threadSize; i++) {
                    addCowTotalTime += executor.submit(new AddCallable(list1, countDownLatch)).get();
                }
                System.out.println("Collections.synchronizedList add method cost time is "+ addCowTotalTime);
    
                for (int i = 0; i < threadSize; i++) {
                    addCsTotalTime += executor.submit(new AddCallable(list2, countDownLatch)).get();
                }
                System.out.println("CopyOnWriteArrayList add method cost time is "+ addCsTotalTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    
        public void get() {
            List<Integer> list = initList();
            List<Integer> list1 = Collections.synchronizedList(list);
            List<Integer> list2 = new CopyOnWriteArrayList<>(list);
            int addCowTotalTime = 0;
            int addCsTotalTime = 0;
            ExecutorService executor = Executors.newFixedThreadPool(threadSize);
            CountDownLatch countDownLatch = new CountDownLatch(threadSize);
            try {
                for (int i = 0; i < threadSize; i++) {
                    addCowTotalTime += executor.submit(new GetCallable(list1, countDownLatch)).get();
                }
                System.out.println("Collections.synchronizedList get method cost time is "+ addCowTotalTime);
    
                for (int i = 0; i < threadSize; i++) {
                    addCsTotalTime += executor.submit(new GetCallable(list2, countDownLatch)).get();
                }
                System.out.println("CopyOnWriteArrayList get method cost time is "+ addCsTotalTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    
        private List<Integer> initList() {
            List<Integer> list = new ArrayList<>();
            int value = new Random().nextInt(1000);
            for (int i = 0; i < num; i++) {
                list.add(value);
            }
            return list;
        }
    
        private class AddCallable implements Callable<Integer> {
            List<Integer> list;
            CountDownLatch countDownLatch;
    
            AddCallable(List<Integer> list, CountDownLatch countDownLatch) {
                this.list = list;
                this.countDownLatch = countDownLatch;
            }
    
            @Override
            public Integer call() throws Exception {
                long start = System.currentTimeMillis();
                int value = new Random().nextInt(1000);
                for (int i = 0; i < num; i++) {
                    list.add(value);
                }
                long end = System.currentTimeMillis();
                countDownLatch.countDown();
                return (int) (end - start);
            }
        }
    
        private class GetCallable implements Callable<Integer> {
            List<Integer> list;
            CountDownLatch countDownLatch;
    
            GetCallable(List<Integer> list, CountDownLatch countDownLatch) {
                this.list = list;
                this.countDownLatch = countDownLatch;
            }
    
            @Override
            public Integer call() throws Exception {
                long start = System.currentTimeMillis();
                int value = new Random().nextInt(num);
                for (int i = 0; i < num; i++) {
                    list.get(value);
                }
                long end = System.currentTimeMillis();
                countDownLatch.countDown();
                return (int) (end - start);
            }
        }
    }
    

    main方法中调用

            CallableUtils utils = new CallableUtils(10000, 2);
            utils.add();
            utils.get();
    

    设置数据总数为10000,线程数为2,运行结果为:

    Collections.synchronizedList add method cost time is 5
    CopyOnWriteArrayList add method cost time is 401
    Collections.synchronizedList get method cost time is 2
    CopyOnWriteArrayList get method cost time is 1
    

    设置数据总数为10000,线程数为6,运行结果为:

    Collections.synchronizedList add method cost time is 8
    CopyOnWriteArrayList add method cost time is 1505
    Collections.synchronizedList get method cost time is 4
    CopyOnWriteArrayList get method cost time is 2
    

    设置数据总数为10000,线程数为10,运行结果为:

    Collections.synchronizedList add method cost time is 7
    CopyOnWriteArrayList add method cost time is 3893
    Collections.synchronizedList get method cost time is 5
    CopyOnWriteArrayList get method cost time is 2
    

    设置数据总数为10000,线程数为16,运行结果为:

    Collections.synchronizedList add method cost time is 12
    CopyOnWriteArrayList add method cost time is 10000
    Collections.synchronizedList get method cost time is 9
    CopyOnWriteArrayList get method cost time is 2
    

    设置数据总数为10000,线程数为22,运行结果为:

    Collections.synchronizedList add method cost time is 14
    CopyOnWriteArrayList add method cost time is 19917
    Collections.synchronizedList get method cost time is 11
    CopyOnWriteArrayList get method cost time is 4
    

    通过上面五组数据可以看出:

    • 写数据时,线程数越多,CopyOnWriteArrayList花费的时间越多,而Collections.synchronizedList花费的时间增长缓慢。也就是线程数越多CopyOnWriteArrayList性能下降非常严重,而Collections.synchronizedList虽然有下降,但是不严重。
    • 读数据时,线程数越多,CopyOnWriteArrayList花费的时间慢慢增多,而Collections.synchronizedList花费时间的增长明显高于CopyOnWriteArrayList。也就是线程数越多CopyOnWriteArrayListCollections.synchronizedList性能都下降,但是Collections.synchronizedList下降的速度更明显。

    因此在使用这两个集合时,根据自己的需要,做出相应的选择。

    那么我们如果如何取舍呢?线程安全是多个线程操作同一对象不会出现问题,而非线程安全是多个线程操作同一对象会出现问题。那么非线程安全要解决这个问题就要使用synchronized来进行同步控制,当然这样也会造成系统性能的降低。所以我们在使用时,如果需求是多个线程操作同一对象,那么就用线程安全的,反之就用非线程安全的。这里存在的一个误区是非线程安全的不能用于多线程,这个其实是错误的,为什么呢?因为我们说的操作同一对象,如果多线程操作的不是同一对象,而是操作各自不同的对象,这样就可以使用了。

    相关文章

      网友评论

          本文标题:线程安全与非线程安全

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