美文网首页IT行业干货大全Android世界java
Java多线程理解:线程安全的集合对象

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

作者: 梦工厂 | 来源:发表于2015-08-21 15:02 被阅读7564次

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只在这一个线程中使用,那么是没问题的。
  • 线程‘安全’的集合对象
    较复杂的操作下,线程安全的集合对象也无法保证数据的同步,仍然需要我们来处理。

[2015-08]

相关文章

  • java文艺复兴

    JAVA 语言基础 理解面向对象OO 常用类 集合 IO 多线程 GUI(??) 网络编程 JAVA WEB (...

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

    1、概念介绍 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行...

  • java_多线程

    java_多线程 线程创建方式;join用法; sleep和wait区别; 线程安全和不安全的java集合; St...

  • 第35节:Java面向对象中的多线程

    Java面向对象中的多线程 多线程 在Java面向对象中的多线程中,要理解多线程的知识点,首先要掌握什么是进程,什...

  • Java 集合

    1、java集合分类 线程安全的集合对象:Vector :是ArrayList的线程安全的实现HashTableS...

  • 关于Java中的 CopyOnWriteArraySet

    最近做的课程设计中用到了Java线程安全的集合类,因此关于对Java线程安全集合类的理解做个简单记录。Java集合...

  • 高并发编程总纲

    个人介绍 Java多线程,各种并发集合,线程安全。 Java锁,性能以及效率。 框架netty。在netty基础上...

  • Java字符串是不变的对象

    不变的对象即不可改变的对象,Java中String 是不可改变的对象。 *安全性 多线程安全

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • Collections

    java提供了一些线程安全的集合类,在多线程程序中可以使用这些线程安全的集合以避免可能的不一致和崩溃现象。 线程安...

网友评论

  • ALemon_Y:最近复习线程,看了收获良多
  • 提丶米:学习了!谢谢!
    打不碎的水:导包错了吧
  • 憩在河岸上的鱼丶:Vector我试了下,输出每次都不一样
  • 007809ecdee8:在你得地四节中4、线程安全的集合并不安全。你等待设置了100个线程却启动了1000个线程,所以输出结果有所出入,我测试设置等待1000个线程则每次都是1000了,是我理解有问题么?

本文标题:Java多线程理解:线程安全的集合对象

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