• Java多线程理解:线程安全的集合对象


    1、概念介绍

    • 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
    • 线程不安全就是不提供数据访问保护,多线程先后更改数据会产生数据不一致或者数据污染的情况。
    • 一般使用synchronized关键字加锁同步控制,来解决线程不安全问题。

    2、线程安全的集合对象

    • ArrayList线程不安全,Vector线程安全;
    • HashMap线程不安全,HashTable线程安全;
    • StringBuilder线程不安全,StringBuffer线程安全。

    3、代码测试

    • ArrayList线程不安全:
      在主线程中新建100个子线程,分别向ArrayList中添加100个元素,最后打印ArrayList的size。

      public class Test {
      
      public static void main(String [] args){
       // 用来测试的List  
       List<String> data = new ArrayList<>();
       // 用来让主线程等待100个子线程执行完毕  
       CountDownLatch countDownLatch = new CountDownLatch(100);
       // 启动100个子线程  
       for(int i=0;i<100;i++){
           SampleTask task = new SampleTask(data,countDownLatch);
           Thread thread = new Thread(task);
           thread.start();
       }
       try{
           // 主线程等待所有子线程执行完成,再向下执行  
           countDownLatch.await();
       }catch (InterruptedException e){  
           e.printStackTrace();  
       } 
       // List的size  
       System.out.println(data.size());
      }
      }
      class SampleTask implements Runnable {
      CountDownLatch countDownLatch;
      List<String> data;
      public SampleTask(List<String> data,CountDownLatch countDownLatch){
         this.data = data;
         this.countDownLatch = countDownLatch;
      }
      @Override
      public void run() {
         // 每个线程向List中添加100个元素  
         for(int i = 0; i < 100; i++)  
         {  
             data.add("1");
         }  
         // 完成一个子线程  
         countDownLatch.countDown();
      }
      }

      7次测试输出():

      9998
      10000
      10000
      ArrayIndexOutOfBoundsException
      10000
      9967
      9936
    • Vector线程安全:
      在主线程中新建100个子线程,分别向Vector中添加100个元素,最后打印Vector的size。

      public class Test {
      
      public static void main(String [] args){
       // 用来测试的List  
       List<String> data = new Vector<>();
       // 用来让主线程等待100个子线程执行完毕  
       CountDownLatch countDownLatch = new CountDownLatch(100);
       // 启动100个子线程  
       for(int i=0;i<100;i++){
           SampleTask task = new SampleTask(data,countDownLatch);
           Thread thread = new Thread(task);
           thread.start();
       }
       try{
           // 主线程等待所有子线程执行完成,再向下执行  
           countDownLatch.await();
       }catch (InterruptedException e){  
           e.printStackTrace();  
       } 
       // List的size  
       System.out.println(data.size());
      }
      }
      class SampleTask implements Runnable {
      CountDownLatch countDownLatch;
      List<String> data;
      public SampleTask(List<String> data,CountDownLatch countDownLatch){
         this.data = data;
         this.countDownLatch = countDownLatch;
      }
      @Override
      public void run() {
         // 每个线程向List中添加100个元素  
         for(int i = 0; i < 100; i++)  
         {  
             data.add("1");
         }  
         // 完成一个子线程  
         countDownLatch.countDown();
      }
      }

      7次测试输出():

      10000
      10000
      10000
      10000
      10000
      10000
      10000
    • 使用synchronized关键字来同步ArrayList:

      public class Test {
      
      public static void main(String [] args){
       // 用来测试的List  
       List<String> data = new ArrayList<>();
       // 用来让主线程等待100个子线程执行完毕  
       CountDownLatch countDownLatch = new CountDownLatch(100);
       // 启动100个子线程  
       for(int i=0;i<100;i++){
           SampleTask task = new SampleTask(data,countDownLatch);
           Thread thread = new Thread(task);
           thread.start();
       }
       try{
           // 主线程等待所有子线程执行完成,再向下执行  
           countDownLatch.await();
       }catch (InterruptedException e){  
           e.printStackTrace();  
       } 
       // List的size  
       System.out.println(data.size());
      }
      }
      class SampleTask implements Runnable {
      CountDownLatch countDownLatch;
      List<String> data;
      public SampleTask(List<String> data,CountDownLatch countDownLatch){
         this.data = data;
         this.countDownLatch = countDownLatch;
      }
      @Override
      public void run() {
         // 每个线程向List中添加100个元素  
         for(int i = 0; i < 100; i++)  
         {  
             synchronized(data){
                 data.add("1");
             }
         }  
         // 完成一个子线程  
         countDownLatch.countDown();
      }
      }

      7次测试输出():

      10000
      10000
      10000
      10000
      10000
      10000
      10000

    3、原因分析

    • ArrayList在添加一个元素的时候,它会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
      在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
      而如果是在多线程情况下,比如有两个线程,线程 A 先将元素1存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B向此 ArrayList 添加元素2,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值,结果Size都等于1。
      最后,ArrayList中期望的元素应该有2个,而实际元素是在0位置,造成丢失元素,故Size 等于1。导致“线程不安全”。
      ArrayList源码:
      @Override public boolean add(E object) {
            Object[] a = array;
            int s = size;
            if (s == a.length) {
                Object[] newArray = new Object[s +
                        (s < (MIN_CAPACITY_INCREMENT / 2) ?
                         MIN_CAPACITY_INCREMENT : s >> 1)];
                System.arraycopy(a, 0, newArray, 0, s);
                array = a = newArray;
            }
            a[s] = object;
            size = s + 1;
            modCount++;
            return true;
        }
    • Vector的所有操作方法都被同步了,既然被同步了,多个线程就不可能同时访问vector中的数据,只能一个一个地访问,所以不会出现数据混乱的情况,所以是线程安全的。
      Vector源码:
      @Override
        public synchronized boolean add(E object) {
            if (elementCount == elementData.length) {
                growByOne();
            }
            elementData[elementCount++] = object;
            modCount++;
            return true;
        }

    4、线程安全的集合并不安全

    分析以下场景:

    synchronized(map){
    Object value = map.get(key);
    if(value == null)
    {
        value = new Object();
        map.put(key,value);
    }
    return value;}

    由于线程安全的集合对象是基于单个方法的同步,所以即使map是线程安全的,也会产生不同步现象。
    在非单个方法的场景下,我们仍然需要使用synchronized加锁才能保证对象的同步。

    代码测试:

    public class Test {
    
      public static void main(String [] args){
          // 用来测试的List  
          List<String> data = new Vector<>();
          // 用来让主线程等待100个子线程执行完毕  
          CountDownLatch countDownLatch = new CountDownLatch(100);
          // 启动100个子线程  
          for(int i=0;i<1000;i++){
              SampleTask task = new SampleTask(data,countDownLatch);
              Thread thread = new Thread(task);
              thread.start();
          }
          try{
              // 主线程等待所有子线程执行完成,再向下执行  
              countDownLatch.await();
          }catch (InterruptedException e){  
              e.printStackTrace();  
          } 
          // List的size  
          System.out.println(data.size());
      }
    }
    class SampleTask implements Runnable {
        CountDownLatch countDownLatch;
        List<String> data;
        public SampleTask(List<String> data,CountDownLatch countDownLatch){
            this.data = data;
            this.countDownLatch = countDownLatch;
        }
        @Override
        public void run() {
            // 每个线程向List中添加100个元素  
            int size = data.size();
            data.add(size,"1"); 
            // 完成一个子线程  
            countDownLatch.countDown();
        }
    }
    997
    993
    995
    996
    997
    998
    997

    5、总结

      • 如何取舍
        线程安全必须要使用synchronized关键字来同步控制,所以会导致性能的降低
        当不需要线程安全时,可以选择ArrayList,避免方法同步产生的开销;
        多个线程操作同一个对象时,可以选择线程安全的Vector;
      • 线程不安全!=不安全
        有人在使用过程中有一个不正确的观点:我的程序是多线程的,不能使用ArrayList要使用Vector,这样才安全。
        线程不安全并不是多线程环境下就不能使用
        注意线程不安全条件:多线程操作同一个对象。比如上述代码就是在多个线程操作同一个ArrayList对象。
        如果每个线程中new一个ArrayList,而这个ArrayList只在这一个线程中使用,那么是没问题的。
      • 线程‘安全’的集合对象
        较复杂的操作下,线程安全的集合对象也无法保证数据的同步,仍然需要我们来处理。
  • 相关阅读:
    android判断服务是否是运行状态
    Android调用OCR识别图像中的文字
    Java生成各种条形码
    android 实现摇一摇功能
    【读书笔记】Html5游戏开发
    SpeechLib 语音播报
    罗盘
    注释文档在线编辑及生成
    系统空闲时间判断&命名验证
    Asp.Net MVC中使用ACE模板之Jqgrid
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/7016007.html
Copyright © 2020-2023  润新知