专栏首页JVMGCJava同步容器
原创

Java同步容器

ArrayList,HashSet,HashMap都是线程非安全的,在多线程环境下,会导致线程安全问题,所以在使用的时候需要进行同步,这无疑增加了程序开发的难度。所以JAVA提供了同步容器

同步容器

  • ArrayList ===> Vector,Stack
  • HashMap ===> HashTable(key,value都不能为空)
  • Collections.synchronizedXXX(List,Set,Map)

Vector实现List接口,底层和ArrayList类似,但是Vector中的方法都是使用synchronized修饰,即进行了同步的措施。 但是,Vector并不是线程安全的。

Stack也是一个同步容器,也是使用synchronized进行同步,继承与Vector,是数据结构中的,先进后出。

HashTableHashMap很相似,但HashTable进行了同步处理。

Collections工具类提供了大量的方法,比如对集合的排序、查找等常用的操作。同时也通过了相关了方法创建同步容器类

Vector

package com.rumenz.task;

import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

//线程安全
public class VectorExample1 {
    public static Integer clientTotal=5000;
    public static Integer thradTotal=200;
    private static List<Integer> list=new Vector<>();

    public static void main(String[] args) throws Exception{

        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(thradTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final Integer j=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    update(j);
                    semaphore.release();

                }catch (Exception e){
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
            
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("size:"+list.size());

    }

    private static void update(Integer j) {
        list.add(j);
    }
}

同步容器不一定就线程安全

package com.rumenz.task;

import scala.collection.convert.impl.VectorStepperBase;

import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

//线程不安全
public class VectorExample2 {
    public static Integer clientTotal=5000;
    public static Integer threadTotal=200;

    private static List<Integer> list=new Vector();
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        for (int i = 0; i < threadTotal; i++) {
            list.add(i);
        }
        for (int i = 0; i < clientTotal; i++) {
            try{
                semaphore.acquire();
                executorService.execute(()->{
                    for (int j = 0; j < list.size(); j++) {
                        list.remove(j);
                    }
                });
                executorService.execute(()->{
                    for (int j = 0; j < list.size(); j++) {
                        list.get(j);
                    }
                });
                semaphore.release();

            }catch (Exception e){
                e.printStackTrace();
            }

            
        }
        executorService.shutdown();
    }

}

运行报错

Exception in thread "pool-1-thread-2" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 36
	at java.util.Vector.get(Vector.java:751)
	at com.rumenz.task.VectorExample2.lambda$main$1(VectorExample2.java:38)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

原因分析

Vector是线程同步容器,size(),get(),remove()都是被synchronized修饰的,为什么会有线程安全问题呢?

get()抛出的异常肯定是remove()引起的,Vector能同时保证同一时刻只有一个线程进入,但是:

//线程1
 executorService.execute(()->{

  for (int j = 0; j < list.size(); j++) {
    list.remove(j);
  }
});
//线程2
executorService.execute(()->{
  for (int j = 0; j < list.size(); j++) {
    list.get(j);
  }
});
  • 线程1和线程2都执行完list.size(),都等于200,并且j=100
  • 线程1执行list.remove(100)操作,
  • 线程2执行list.get(100)就会抛出数组越界的异常。

同步容器虽然是线程安全的,但是不代表在任何环境下都是线程安全的。

HashTable

线程安全,key,value都不能为null。在修改数据时锁住整个HashTable,效率低下。初始size=11

package com.rumenz.task;

import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

//线程安全
public class HashTableExample1 {

    public static Integer clientTotal=5000;
    public static Integer threadTotal=200;
    private static Map<Integer,Integer> map=new Hashtable<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final Integer j=i;
            try{
               semaphore.acquire();
               update(j);
               semaphore.release();
            }catch (Exception e){
                e.printStackTrace();
            }
            countDownLatch.countDown();
            
        }
        countDownLatch.await();

        executorService.shutdown();
        System.out.println("size:"+map.size());


    }

    private static void update(Integer j) {
        map.put(j, j);
    }
}

//size:5000

Collections.synchronizedList线程安全

package com.rumenz.task.Collections;

import com.google.common.collect.Lists;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
//线程安全
public class synchronizedExample {
    public static Integer clientTotal=5000;
    public static Integer threadTotal=200;

    private static List<Integer> list=Collections.synchronizedList(Lists.newArrayList());

    public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final Integer j=i;
             try{
                 semaphore.acquire();
                 update(j);
                 semaphore.release();
             }catch (Exception e){
                 e.printStackTrace();
             }
             countDownLatch.countDown();
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("size:"+list.size());

    }

    private static void update(Integer j) {
        list.add(j);
    }
}
//size:5000

Collections.synchronizedSet线程安全

package com.rumenz.task.Collections;

import com.google.common.collect.Lists;
import org.assertj.core.util.Sets;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

//线程安全
public class synchronizedSetExample {

    public static Integer clientTotal=5000;
    public static Integer threadTotal=200;

    private static Set<Integer> set=Collections.synchronizedSet(Sets.newHashSet());

    public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final Integer j=i;
             try{
                 semaphore.acquire();
                 update(j);
                 semaphore.release();
             }catch (Exception e){
                 e.printStackTrace();
             }
             countDownLatch.countDown();
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("size:"+set.size());

    }

    private static void update(Integer j) {
        set.add(j);
    }
}
//size:5000

Collections.synchronizedMap线程安全

package com.rumenz.task.Collections;

import org.assertj.core.util.Sets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class synchronizedMapExample {

    public static Integer clientTotal=5000;
    public static Integer threadTotal=200;

    private static Map<Integer,Integer> map=Collections.synchronizedMap(new HashMap<>());

    public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final Integer j=i;
            try{
                semaphore.acquire();
                update(j);
                semaphore.release();
            }catch (Exception e){
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("size:"+map.size());

    }

    private static void update(Integer j) {
        map.put(j, j);
    }
}
//size:5000

Collections.synchronizedXXX在迭代的时候,需要开发者自己加上线程锁控制代码,因为在整个迭代过程中循环外面不加同步代码,在一次次迭代之间,其他线程对于这个容器的add或者remove会影响整个迭代的预期效果,这个时候需要在循环外面加上synchronized(XXX)

集合的删除

  • 如果在使用foreach或iterator进集合的遍历,
  • 尽量不要在操作的过程中进行remove等相关的更新操作。
  • 如果非要进行操作,则可以在遍历的过程中记录需要操作元素的序号,
  • 待遍历结束后方可进行操作,让这两个动作分开进行
package com.rumenz.task;

import com.google.common.collect.Lists;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;


public class CollectionsExample {

    private static List<Integer> list=Collections.synchronizedList(Lists.newArrayList());

    public static void main(String[] args) {
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        //del1();
        //del2();
        del3();

    }

    private static void del3() {
        for(Integer i:list){

            if(i==4){
                list.remove(i);
            }
        }

    }
    //Exception in thread "main" java.util.ConcurrentModificationException
    private static void del2() {
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            if(i==4){
                list.remove(i);
            }
        }

    }
    //Exception in thread "main" java.util.ConcurrentModificationException
    private static void del1() {
        for (int i = 0; i < list.size(); i++) {
            if(list.get(i)==4){
                list.remove(i);
            }
        }

    }
}

在单线程会出现以上错误,在多线程情况下,并且集合时共享的,出现异常的概率会更大,需要特别的注意。解决方案是希望在foreach或iterator时,对要操作的元素进行标记,待循环结束之后,在执行相关操作。

总结

同步容器采用synchronized进行同步,因此执行的性能会受到影响,并且同步容器也并不一定会做到线程安全。

wx.jpg

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java同步容器和并发容器

    同步容器的同步原理就是在方法上用 synchronized 修饰。那么,这些方法每次只允许一个线程调用执行。

    李红
  • Java Concurrent -- 同步容器类

    同步容器类包括Vector和Hashtable,其外还包括一些由Collections。synchronizedXxx()等工厂方法创建的同步封装器类。这些类实...

    SuperHeroes
  • Java并发-同步容器篇

    官人们好啊,我是汤圆,今天给大家带来的是《Java并发-同步容器篇》,希望有所帮助,谢谢

    汤圆学Java
  • Java并发编程:同步容器

    Java并发编程:同步容器   为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchroni...

    陈树义
  • Java并发编程:同步容器

      为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchronizer(比如CountDow...

    陈树义
  • 同步容器与并发容器

    Vector、HashTable -- JDK提供的同步容器类 Collections.synchronizedXXX 本质是对相应的容器进行包装

    Dream城堡
  • Java多线程并发之同步容器和并发容器-第一篇

    本文主要讲解在Java多线程并发开发中,集合中有哪些支持并发的的。什么是同步容器(集合),什么是并发容器(集合)?并发容器分类有哪些?每个分类都有哪些类?

    凯哥Java
  • 并发-7-同步容器和ConcurrentHashMap

    众所周知,很多书上,我们看到Arraylist并不是线程安全的,Vector是线程安全的。

    全栈程序员站长
  • Java同步器之AbstractOwnableSynchronizer详解

    JDK 6 时提供。 一种同步器,可以由一个线程独占。该类提供了创建锁和相关同步器的基础,这些同步器可能包含所有权的概念。AbstractOwnableSyn...

    JavaEdge
  • 并发编程之同步容器类和并发容器类

    一、fail-fast机制 快速报错机制(fail-fast)能够防止多个进程同时修改同一个容器的内容。如果在你迭代遍历某个容器的过程中,另一个进程接入其中,...

    lyb-geek
  • Java并发-15.同步器简介

    悠扬前奏
  • 同步器

    Java提供两种同步机制,一种是内置的synchronize,另外一种就是大名鼎鼎的AQS,基于AQS实现了很多同步器:倒数闩锁(CountDownLatch)...

    搬砖俱乐部
  • Docker学习之宿机容器时区同步

    数据库挂掉以后,登录天兔查询监控时间,发现一些监控数据时间极其不准确,经查询原来容器采用的UTC时区,导致宿主机和容器两者之间的时间相差了八个小时!

    小柒2012
  • Docker学习之宿机容器时区同步

    数据库挂掉以后,登录天兔查询监控时间,发现一些监控数据时间极其不准确,经查询原来容器采用的UTC时区,导致宿主机和容器两者之间的时间相差了八个小时!

    小柒2012
  • Java并发之-队列同步器AQS

    AQS是AbstractQueuedSynchronizer的简称,是用来构建锁或者其他同步组建的基础框架,它使用一个 int 类型的成员变量来表示同步状态,通...

    胖虎
  • Docker容器学习梳理-容器时间跟宿主机时间同步

    在Docker容器创建好之后,可能会发现容器时间跟宿主机时间不一致,这就需要同步它们的时间,让容器时间跟宿主机时间保持一致。如下: 宿主机时间 [root@sl...

    洗尽了浮华
  • java之Synchronized同步

    其实这个技术点的使用一点也不难,需要保证方法同步就在方法上加上Synchronized关键字就行,为什么今天自己还要单独抽取一点时间去写这篇呢?其实在这说下,每...

    码农王同学
  • java FTP同步工具

    葫芦
  • Java 同步 synchronized与lock

    实现线程同步一个使synchronized关键字,一个是通过对象lock. Lock 在jdk 1.5才出现的,在一定程度上缓解了synchronized同...

    张凝可

扫码关注云+社区

领取腾讯云代金券