Java 并发编程(8) —— 同步容器

1 同步容器出现的原因


常见的容器中,如:HashMap、ArrayList、LinkedList、HashSet 等都不是线程安全的容器。

所以,Java 提供了 HashTable、Vector 等同步的容器供用户使用。


2 Java中的同步容器类


  在Java中,同步容器主要包括2类:

  1)Vector、Stack、HashTable

  2)Collections类中提供的静态工厂方法创建的类

  Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。

  Stack也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。

  HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。

  Collections类是一个工具提供类,注意,它和Collection不同,Collection是一个顶层的接口。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。最重要的是,在它里面提供了几个静态工厂方法来创建同步容器类,如下图所示:

这里写图片描述


3 同步容器的缺陷


  从同步容器的具体实现源码可知,同步容器中的方法采用了synchronized进行了同步,那么很显然,这必然会影响到执行性能,另外,同步容器就一定是真正地完全线程安全吗?不一定,这个在下面会讲到。

  我们首先来看一下传统的非同步容器和同步容器的性能差异,我们以ArrayList和Vector为例:


3.1 性能问题

  我们先通过一个例子看一下Vector和ArrayList在插入数据时性能上的差异:

package sync_container;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Vector;

/**
 * Author:  HeatDeath
 * Date:    2018/2/5
 * Desc:
 */
public class SyncContainer {
    public static void singleThread() {
        ArrayList<Integer> single_list = new ArrayList<Integer>();
        Vector<Integer> single_vector = new Vector<Integer>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++)
            single_list.add(i);
        long end = System.currentTimeMillis();
        System.out.println("ArrayList进行【10000000】次插入操作耗时:" + (end - start) + "ms");
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++)
            single_vector.add(i);
        end = System.currentTimeMillis();
        System.out.println("Vector进行【10000000】次插入操作耗时:" + (end - start) + "ms");
        System.out.println("--------------------------------------------------------");
    }

    public static void mulThread() {
        final ArrayList<Integer> list = new ArrayList<Integer>();
        final Vector<Integer> vector = new Vector<Integer>();
        for (int i = 0; i < 10; i++) {
            Thread thread1 = new Thread() {
                public void run() {
                    long start = System.currentTimeMillis();
                    for (int i = 0; i < 100000; i++)
                        list.add(i);
                    long end = System.currentTimeMillis();
                    System.out.println("ArrayList进行100000次插入操作耗时:" + (end - start) + "ms");
                }
            };
            thread1.start();
        }

        for (int i = 0; i < 10; i++) {
            Thread thread2 = new Thread() {
                public void run() {
                    long start = System.currentTimeMillis();
                    for (int i = 0; i < 100000; i++)
                        vector.add(i);
                    long end = System.currentTimeMillis();
                    System.out.println("Vector进行100000次插入操作耗时:" + (end - start) + "ms");
                }
            };
            thread2.start();
        }
    }

    public static void main(String[] args) {
        SyncContainer.singleThread();
        SyncContainer.mulThread();
    }
}

分别以单线程和多线程的方式测试了 ArrayList 和 Vector 两种容器。

运行结果如下:

ArrayList进行【10000000】次插入操作耗时:3958ms
Vector进行【10000000】次插入操作耗时:1593ms
--------------------------------------------------------
Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 9369
    at java.util.ArrayList.add(ArrayList.java:463)
    at sync_container.SyncContainer$1.run(SyncContainer.java:37)
java.lang.ArrayIndexOutOfBoundsException: 549
    at java.util.ArrayList.add(ArrayList.java:463)
    at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-8" java.lang.ArrayIndexOutOfBoundsException: 31618
    at java.util.ArrayList.add(ArrayList.java:463)
    at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-9" java.lang.ArrayIndexOutOfBoundsException: 31618
    at java.util.ArrayList.add(ArrayList.java:463)
    at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 106710
    at java.util.ArrayList.add(ArrayList.java:463)
    at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-6" java.lang.ArrayIndexOutOfBoundsException: 240097
    at java.util.ArrayList.add(ArrayList.java:463)
    at sync_container.SyncContainer$1.run(SyncContainer.java:37)
ArrayList进行100000次插入操作耗时:44ms
ArrayList进行100000次插入操作耗时:44ms
ArrayList进行100000次插入操作耗时:56ms
ArrayList进行100000次插入操作耗时:54ms
Vector进行100000次插入操作耗时:77ms
Vector进行100000次插入操作耗时:92ms
Vector进行100000次插入操作耗时:104ms
Vector进行100000次插入操作耗时:104ms
Vector进行100000次插入操作耗时:107ms
Vector进行100000次插入操作耗时:107ms
Vector进行100000次插入操作耗时:111ms
Vector进行100000次插入操作耗时:111ms
Vector进行100000次插入操作耗时:116ms
Vector进行100000次插入操作耗时:115ms

Process finished with exit code 0

在数据量较大的时候,且为【单线程】的情况,Vector 比 ArrayList 的性能更好,Vector 上锁解锁的时间可以忽略不计。而 Vector 和 ArrayList 都是可变数组,当容量不足的时候,ArrayList 是容量增大 1.5 倍,而 Vector 是容量增大 2 倍。Vector 在增加容量上节省了更多的时间。


3.2 同步容器真的是安全的吗?

  也有有人认为Vector中的方法都进行了同步处理,那么一定就是线程安全的,事实上这可不一定。看下面这段代码:

package sync_container;

import java.util.Vector;

/**
 * Author:  HeatDeath
 * Date:    2018/2/5
 * Desc:
 */
public class VectorTest {
    static Vector<Integer> vector = new Vector<Integer>();
    public static void main(String[] args) throws InterruptedException {
        while(true) {
            for(int i=0;i<10;i++)
                vector.add(i);
            Thread thread1 = new Thread(){
                public void run() {
                    for(int i=0;i<vector.size();i++)
                        vector.remove(i);
                };
            };
            Thread thread2 = new Thread(){
                public void run() {
                    for(int i=0;i<vector.size();i++)
                        vector.get(i);
                };
            };
            thread1.start();
            thread2.start();
            while(Thread.activeCount()>10)   {

            }
        }
    }
}

运行结果如下:

Exception in thread "Thread-10397" Exception in thread "Thread-10389" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3
    at java.util.Vector.get(Vector.java:748)
    at sync_container.VectorTest$2.run(VectorTest.java:25)
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
    at java.util.Vector.get(Vector.java:748)
    at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-10417" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 20
    at java.util.Vector.get(Vector.java:748)
    at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-10461" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
    at java.util.Vector.get(Vector.java:748)
    at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-10757" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9
    at java.util.Vector.get(Vector.java:748)
    at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-11035" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 15
    at java.util.Vector.get(Vector.java:748)
    at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-11051" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 21

这里写图片描述

  正如大家所看到的,这段代码报错了:数组下标越界。

  也许有朋友会问:Vector是线程安全的,为什么还会报这个错?很简单,对于Vector,虽然能保证每一个时刻只能有一个线程访问它,但是不排除这种可能:

  当某个线程在某个时刻执行这句时:

for(int i=0;i<vector.size();i++)
    vector.get(i);

  假若此时vector的size方法返回的是10,i的值为9

  然后另外一个线程执行了这句:

for(int i=0;i<vector.size();i++)
    vector.remove(i);

  将下标为9的元素删除了。

  那么通过get方法访问下标为9的元素肯定就会出问题了。

  因此为了保证线程安全,必须在方法调用端做额外的同步措施,如下面所示:

package sync_container;

import java.util.Vector;

/**
 * Author:  HeatDeath
 * Date:    2018/2/5
 * Desc:
 */
public class VectorTest {
    static Vector<Integer> vector = new Vector<Integer>();

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            for (int i = 0; i < 10; i++)
                vector.add(i);
            Thread thread1 = new Thread() {
                public void run() {
                    synchronized (VectorTest.class) {   //进行额外的同步
                        for (int i = 0; i < vector.size(); i++)
                            vector.remove(i);
                    }
                }
            };
            Thread thread2 = new Thread() {
                public void run() {
                    synchronized (VectorTest.class) {
                        for (int i = 0; i < vector.size(); i++)
                            System.out.println(vector.get(i));
                    }
                }
            };
            thread1.start();
            thread2.start();
            while (Thread.activeCount() > 10) {

            }
        }
    }
}

Java并发编程:同步容器
http://www.cnblogs.com/dolphin0520/p/3933404.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值