java高并发(十一)同步容器

本文深入探讨Java中的同步容器,如Vector、Stack、HashTable及其线程安全性问题。通过实例展示同步容器在多线程环境下的使用及潜在风险,强调正确同步的重要性。

上面一节我们介绍了ArrayList、HashSet、HashMap这些容器都是非线程安全的。如果有多个线程并发访问这些容器时,就会触发线程安全问题。因此在编写程序的时候,必须要求开发人员手动的在任何访问到这些容器的地方进行同步处理,这样就导致使用起来非常不便。因此java提供了同步容器方便使用。

在java中同步容器主要包括两类:

  • ArrayList -> Vector,Stack;     HashMap -> HashTable(key,value不能为null)
  • Collections.synchronizedXXX(List, Set, Map)

 Vector

Vector实现了List接口,实际上就是一个数组。与ArrayList非常类似。但是Vector中的所有方法都是使用synchronized方法修饰的方法,进行了同步的措施。因此在多线程环境下使用ArrayList对象时,如果被多个线程共享使用可以换成同步的Vector,这样的话线程安全型会更好一些(而不是完全线程安全的)。

@Slf4j
@ThreadSafe
public class VectorExample1 {
    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    private static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws InterruptedException {
        //线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //定义信号量
        final Semaphore semaphore = new Semaphore(threadTotal);
        //定义计数器
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for(int i = 0; i < clientTotal; i++) {
            final int count  = i;
            executorService.execute(() ->{
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (InterruptedException e) {

                    log.error("exception", e);
                }
                countDownLatch.countDown();

            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}",list.size()) ;
    }

    public static void update(int i) {
        list.add(i);
    }

}

这样输出的结果就是预期的结果。

为什么说同步容器不是线程安全的?

@NotThreadSafe
public class VectorExample2 {
    private static Vector<Integer> vector = new Vector<>();

    public static void main(String[] args) {
        while (true){
            for (int i = 0;i < 10;i++) {
                vector.add(i);
            }
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    for (int i = 0;i < vector.size();i++) {
                        vector.remove(i);
                    }
                }
            };
            Thread thread2 = new Thread(){
                @Override
                public void run() {
                    for (int i = 0;i < vector.size();i++) {
                        vector.get(i);
                    }
                }
            };
            thread1.start();
            thread2.start();
        }

    }
}

运行,抛出异常:

Exception in thread "Thread-611" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 5
	at java.util.Vector.get(Vector.java:748)
	at com.vincent.example.syncContainer.VectorExample2$2.run(VectorExample2.java:25)
Exception in thread "Thread-1759" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 21
	at java.util.Vector.get(Vector.java:748)
	at com.vincent.example.syncContainer.VectorExample2$2.run(VectorExample2.java:25)

原因:get发生越界肯定是remove方法引起的,vector虽然能保证同一时刻只能有一个线程能访问他,但是不排除有这种可能:当某个线程某个时刻执行到int i = 0;i < vector.size()时,vector.size()返回10,i=9;而另外一个线程正好将i=9的vector移除掉了,这时get方法想调用i=9的元素就会出现数组越界的异常。

这个例子演示了两个同步容器的两个同步方法因为操作顺序的差异,在不同线程里面可能会触发线程不安全的问题。因此为了保证线程安全,必须在方法调用端做一些额外的同步措施才可以。在使用同步容器时并不是在所有场合下都是线程安全的。

Stack

Stack中的方法也使用了synchronized修饰了,实际上Stack类继承了Vector类。

HashTable

HashTable实现了Map接口,与HashMap很相似,但是HashTable进行了同步处理,方法也是使用了synchronized进行了修饰。但是在使用HashTable时一定要注意key和value是不能为null的。

Collections

将新建ArrayList、HashSet、HashMap对象由Collections产生:

private static List<Integer> list = Collections.synchronizedList(new ArrayList<>());
private static Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
private static Map<Integer, Integer> map = Collections.synchronizedMap(new HashMap<>());

在集合遍历过程中删除操作

public class VectorExample3 {

    //Exception in thread "main" java.util.ConcurrentModificationException
    private static void test1(Vector<Integer> v1){ //foreach
        for(Integer i: v1){
            if(i.equals(3)){
                v1.remove(i);
            }
        }
    }
    //Exception in thread "main" java.util.ConcurrentModificationException
    private static void test2(Vector<Integer> v1){ //iterator
        Iterator<Integer> integerIterator = v1.iterator();
        while (integerIterator.hasNext()) {
            Integer i = integerIterator.next();
            if(i.equals(3)){
                v1.remove(i);
            }
        }
    }
    // success
    private static void test3(Vector<Integer> v1){
        for(int i = 0; i < v1.size(); i++) {
            if(v1.equals(3)){
                v1.remove(i);
            }
        }
    }

    public static void main(String[] args) {
        Vector<Integer> vector = new Vector<>();
        vector.add(1);
        vector.add(2);
        vector.add(3);
        test1(vector);
    }
}

如果使用了Foreach或者迭代器来循环我们的集合时,尽量不要在循环中做集合的删除操作,如果要做remove操作时,建议在遍历的过程中发现需要删除的值然后做一个标记,在遍历结束后在执行相应的remove操作。在多线程情况下出现异常的情况会更大。

一般情况下我们通常使用并发容器来代替同步容器

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值