线程安全与非线程安全

2018-08-31  本文已影响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

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

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

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

猜你喜欢

热点阅读