java杂谈之并发容器

本文介绍了Java中的并发容器,包括 ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentLinkedQueue等,强调了它们在线程安全性和适用场景中的特点。例如,ConcurrentHashMap在并发场景下避免了HashMap的线程安全问题,CopyOnWriteArrayList适合读多写少的场景。总结了各种并发容器的使用场景和核心方法。
摘要由CSDN通过智能技术生成

常用容器

前几天和同事xhf、zm走查代码,功能是为了减少频繁你创建FTP开销用线程notify和wait实现了一个FTP池子,当时提的建议就是用java自带的线程集合实现可能更高效,本文整理下JDK自带线程安全的集合,不考虑多线程并发的情况下,容器类一般使用 ArrayList、HashMap 等线程不安全的类,效率更高。在并发场景下,常会用到ConcurrentHashMap、ArrayBlockingQueue 等线程安全的容器类,虽然牺牲了一些效率,但却得到了安全。

什么是线程安全:

线程安全一般指的就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问。

线程非安全用hashmap举例试下:

public class TestThreadSafe {
    private Map<String, Integer> persons = new HashMap<>();
    private AtomicInteger count = new AtomicInteger(0);

    @Test
    public void test() throws Exception {
        for (int i = 0; i < 10000; i++) {
            int age = i;
            new Thread(()->addName("steven"+ age, age)).start();
        }
        TimeUnit.SECONDS.sleep(10);
        System.out.println("count is:"+count.get()+",persons:"+persons.size());
    }

    private void addName(String name, int age){
        persons.put(name, age);
        count.addAndGet(1);
    }
}
输出:
count is:10000,persons:9996

可以看到addName方法执行了10000次但是真正添加成功的有9996次,这就是由于多线程并发put时会因为size++问题导致覆盖问题(jdk8,jdk7时当并发执行扩容操作时会造成环形链和数据丢失的情况)使用concurrenthashmap时就不会出现此线程安全问题。

1.ConcurrentHashMap 并发版 HashMap

最常见的并发容器之一,可以用作并发场景下的缓存。底层依然是哈希表,但在 JAVA 8 中有了不小的改变,而 JAVA 7 和 JAVA 8 都是用的比较多的版本,因此经常会将这两个版本的实现方式做一些比较(比如面试中)。

一个比较大的差异就是,JAVA 7 中采用分段锁来减少锁的竞争,JAVA 8 中放弃了分段锁,采用 CAS(一种乐观锁),同时为了防止哈希冲突严重时退化成链表(冲突时会在该位置生成一个链表,哈希值相同的对象就链在一起),会在链表长度达到阈值(8)后转换成红黑树(比起链表,树的查询效率更稳定)。

除了key和value不能为null外,其余方法和hashMap几乎一样

常用方法

@Test
public void test_function() throws Exception {
    ConcurrentHashMap<String, String> data = new ConcurrentHashMap<>();

    data.put("Steven","18");

    System.out.println(data.get("Steven"));
}
2.CopyOnWriteArrayList 并发版 ArrayList

并发版 ArrayList,底层结构也是数组,和 ArrayList 不同之处在于:当新增和删除元素时会创建一个新的数组,在新的数组中增加或者排除指定对象,最后用新增数组替换原来的数组。

适用场景:由于读操作不加锁,写(增、删、改)操作加锁,因此适用于读多写少的场景。

局限:由于读的时候不会加锁(读的效率高,就和普通 ArrayList 一样),读取的当前副本,因此可能读取到脏数据。

核心方法可以看出add元素时加锁同时复制了一个数组:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

常用方法:

@T
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值