zoukankan      html  css  js  c++  java
  • Java线程安全和非线程安全

    ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?这些都是Java面试中常见的基础问题。面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuffer是线程安全的。因为这是昨晚刚背的《Java面试题大全》上面写的。此时如果继续问:什么是线程安全?线程安全和非线程安全有什么区别?分别在什么情况下使用?这样一连串的问题,一口老血就喷出来了…

    非线程安全的现象模拟

    这里就使用ArrayList和Vector二者来说明。

    下面的代码,在主线程中new了一个非线程安全的ArrayList,然后开1000个线程分别向这个ArrayList里面添加元素,每个线程添加100个元素,等所有线程执行完成后,这个ArrayList的size应该是多少?应该是100000个?

    package com.enation.newtest.test1125;
    
    import java.util.*;
    import java.util.concurrent.CountDownLatch;
    
    public class Main{
        public static void main(String[] args){
            // 进行10次测试
            for(int i = 0; i < 10; i++){
                test();
            }
        }
        
        public static void test(){
            // 用来测试的List
            List<Object> list = new ArrayList<Object>();
            
            // 线程数量(1000)
            int threadCount = 1000;
            
            // 用来让主线程等待threadCount个子线程执行完毕
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            
            // 启动threadCount个子线程
            for(int i = 0; i < threadCount; i++){
                Thread thread = new Thread(new MyThread(list, countDownLatch));
                thread.start();
            }
            
            try{
                // 主线程等待所有子线程执行完成,再向下执行
                countDownLatch.await();
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
            
            // List的size
            System.out.println(list.size());
            
            
        }
    }
    
    class MyThread implements Runnable{
        private List<Object> list;
        
        private CountDownLatch countDownLatch;
        
        public MyThread(List<Object> list, CountDownLatch countDownLatch){
            this.list = list;
            this.countDownLatch = countDownLatch;
        }
        
        public void run(){
            // 每个线程向List中添加100个元素
            for(int i = 0; i < 100; i++){
                list.add(new Object());
            }
            
            // 完成一个子线程
            countDownLatch.countDown();
        }
    }
    
    
    class Counter {  
        private int count = 0;  
      
        public int getCount() {  
            return count;  
        }  
      
        public void addCount() {  
            count++;  
        }  
    } 

    上面进行了10次测试(为什么要测试10次?因为非线程安全并不是每次都会导致问题)。

    输出结果:

    99946

    100000

    100000

    100000

    99998

    99959

    100000

    99975

    100000

    99996

    上面的输出结果发现,并不是每次测试结果都是100000,有好几次测试最后ArrayList的size小于100000,甚至时不时会抛出个IndexOutOfBoundsException异常。(如果没有这个现象可以多试几次)

    这就是非线程安全带来的问题了。上面的代码如果用于生产环境,就会有隐患就会有BUG了。

    再用线程安全的Vector来进行测试,上面代码改变一处,test()方法中

     

      List<Object> list = new ArrayList<Object>();  

    改成

      List<Object> list = new Vector<Object>();  


    再运行程序。

    输出结果:

    100000

    100000

    100000

    100000

    100000

    100000

    100000

    100000

    100000

    100000

    再多跑几次,发现都是100000,没有任何问题。因为Vector是线程安全的,在多线程操作同一个Vector对象时,不会有任何问题。

    再换成LinkedList试试,同样还会出现ArrayList类似的问题,因为LinkedList也是非线程安全的。

    二者如何取舍

    非线程安全是指多线程操作同一个对象可能会出现问题。而线程安全则是多线程操作同一个对象不会有问题。

    线程安全必须要使用很多synchronized关键字来同步控制,所以必然会导致性能的降低

    所以在使用的时候,如果是多个线程操作同一个对象,那么使用线程安全的Vector;否则,就使用效率更高的ArrayList。

    非线程安全!=不安全

    有人在使用过程中有一个不正确的观点:我的程序是多线程的,不能使用ArrayList要使用Vector,这样才安全。

    非线程安全并不是多线程环境下就不能使用。注意我上面有说到:多线程操作同一个对象。注意是同一个对象。比如最上面那个模拟,就是在主线程中new的一个ArrayList然后多个线程操作同一个ArrayList对象。

    如果是每个线程中new一个ArrayList,而这个ArrayList只在这一个线程中使用,那么肯定是没问题的。

    线程安全的实现

    线程安全是通过线程同步控制来实现的,也就是synchronized关键字。   

    在这里,我用代码分别实现了一个非线程安全的计数器和线程安全的计数器Counter,并对他们分别进行了多线程测试。

    非线程安全的计数器:

    public class Main
    {
        public static void main(String[] args)
        {
            // 进行10次测试
            for(int i = 0; i < 10; i++)
            {
                test();
            }
        }
        
        public static void test()
        {
            // 计数器
            Counter counter = new Counter();
            
            // 线程数量(1000)
            int threadCount = 1000;
            
            // 用来让主线程等待threadCount个子线程执行完毕
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            
            // 启动threadCount个子线程
            for(int i = 0; i < threadCount; i++)
            {
                Thread thread = new Thread(new MyThread(counter, countDownLatch));
                thread.start();
            }
            
            try
            {
                // 主线程等待所有子线程执行完成,再向下执行
                countDownLatch.await();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            
            // 计数器的值
            System.out.println(counter.getCount());
        }
    }
    
    class MyThread implements Runnable
    {
        private Counter counter;
        
        private CountDownLatch countDownLatch;
        
        public MyThread(Counter counter, CountDownLatch countDownLatch)
        {
            this.counter = counter;
            this.countDownLatch = countDownLatch;
        }
        
        public void run()
        {
            // 每个线程向Counter中进行10000次累加
            for(int i = 0; i < 10000; i++)
            {
                counter.addCount();
            }
            
            // 完成一个子线程
            countDownLatch.countDown();
        }
    }
    
    class Counter
    {
        private int count = 0;
    
        public int getCount()
        {
            return count;
        }
    
        public void addCount()
        {
            count++;
        }
    }

    上面的测试代码中,开启1000个线程,每个线程对计数器进行10000次累加,最终输出结果应该是10000000。

    但是上面代码中的Counter未进行同步控制,所以非线程安全。

    输出结果:

    9963727

    9973178

    9999577

    9987650

    9988734

    9988665

    9987820

    9990847

    9992305

    9972233

    稍加修改,把Counter改成线程安全的计数器:

    class Counter
    {
        private int count = 0;
    
        public int getCount()
        {
            return count;
        }
    
        public synchronized void addCount()
        {
            count++;
        }
    }

    上面只是在addCount()方法中加上了synchronized同步控制,就成为一个线程安全的计数器了。再执行程序。

    输出结果:

    10000000

    10000000

    10000000

    10000000

    10000000

    10000000

    10000000

    10000000

    10000000

    10000000

     但是加了同步锁之后,效率明细会慢很多!!!

    作者:叉叉哥   转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/8934832 感谢原文作者的奉献

  • 相关阅读:
    LeetCode 1245. Tree Diameter
    LeetCode 1152. Analyze User Website Visit Pattern
    LeetCode 1223. Dice Roll Simulation
    LeetCode 912. Sort an Array
    LeetCode 993. Cousins in Binary Tree
    LeetCode 1047. Remove All Adjacent Duplicates In String
    LeetCode 390. Elimination Game
    LeetCode 1209. Remove All Adjacent Duplicates in String II
    LeetCode 797. All Paths From Source to Target
    LeetCode 1029. Two City Scheduling
  • 原文地址:https://www.cnblogs.com/jiafuwei/p/6102362.html
Copyright © 2011-2022 走看看