JUC(三)——分段锁机制

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> JUC(三)——分段锁机制

HashMap和HashTable

我们知道HashMap是线程不安全的,在多线程环境下,使用HashMap进行put操作时,可能会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

HashTable和HashMap的实现原理几乎一样,差别无非是HashTable不允许key和value为null,且HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put等所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

ConcurrentHashMap

在JDK1.5以后,java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。ConcurrentHashMap就是其中一种比较常用的类,它是一种线程安全的哈希表,对于多线程的操作,介于HashMap和HashTable之间,其内部采用“分段锁”机制代替了HashTable的“独占锁”,从而大大提高了性能。

分段锁机制

我们知道Map的结构,是采用数组+链表的结构实现的(JDK1.8改采用数组+链表+红黑树实现),而ConcurrentHashMap采用了一种“分段锁”来实现的。

JUC(三):分段锁机制

ConcurrentHashMap

ConcurrentHashMap使用Segment(分段锁)技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。所以说,ConcurrentHashMap在并发情况下,不仅保证了线程安全,而且提高了性能。

CopyOnWriteArrayList

在java.util.concurrent包中还提供了设计用于多线程上下文中的Collection实现,如ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。当期望许多线程访问一个给定collection时,ConcurrentHashMap通常优于同步的HashMap,ConcurrentSkipListMap通常优于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。

接下来简单看一下这个CopyOnWriteArrayList

12345678910111213141516171819
public class CopyOnWriteArrayListDemo implements Runnable {         private static ListString list = Collections.synchronizedList(new ArrayList());     static {        list.add("a");        list.add("b");        list.add("c");    }     @Override    public void run() {        IteratorString iterator = list.iterator();        while (iterator.hasNext()) {            System.out.println(iterator.next());            list.add("a");        }    }}

public class CopyOnWriteArrayListDemo implements Runnable {


    private static ListString list = Collections.synchronizedList(new ArrayList());

static {
    list.add("a");
    list.add("b");
    list.add("c");
}

@Override
public void run() {
    IteratorString iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
        list.add("a");
    }
}

}

123456789
public class CopyOnWriteArrayListTest {     public static void main(String[] args) {        CopyOnWriteArrayListDemo copyOnWriteArrayListDemo = new CopyOnWriteArrayListDemo();        for (int i = 0; i  10; i++) {            new Thread(copyOnWriteArrayListDemo).start();        }    }}

public class CopyOnWriteArrayListTest {


public static void main(String[] args) {
    CopyOnWriteArrayListDemo copyOnWriteArrayListDemo = new CopyOnWriteArrayListDemo();
    for (int i = 0; i  10; i++) {
        new Thread(copyOnWriteArrayListDemo).start();
    }
}

}

测试运行结果

JUC(三):分段锁机制

在程序中,我们创建一个ArrarList,并通过Collections工具类将其转换为线程安全的类,然后创建10个线程,进行迭代并添加。程序运行结果报错,这个错的意思是,并发修改异常,也就是在程序中,因为是同步的,所以一个线程在迭代数据的同时,又要进行添加操作,那么就会出现此异常。那么我们如何解决这个问题呢?

1234567891011121314151617181920
public class CopyOnWriteArrayListDemo implements Runnable {     //    private static ListString list = Collections.synchronizedList(new ArrayList());    private static CopyOnWriteArrayListString list = new CopyOnWriteArrayList();     static {        list.add("a");        list.add("b");        list.add("c");    }     @Override    public void run() {        IteratorString iterator = list.iterator();        while (iterator.hasNext()) {            System.out.println(iterator.next());            list.add("a");        }    }}

public class CopyOnWriteArrayListDemo implements Runnable {


//    private static ListString list = Collections.synchronizedList(new ArrayList());
private static CopyOnWriteArrayListString list = new CopyOnWriteArrayList();

static {
    list.add("a");
    list.add("b");
    list.add("c");
}

@Override
public void run() {
    IteratorString iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
        list.add("a");
    }
}

}

此时再次运行程序,发现异常没有了。那CopyOnWriteArrayList是如何做到的呢?先看一下一小段源码。

1234567891011121314
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();    }}

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();
}
}

这是CopyOnWriteArrayList的添加操作,可以看出,在每次进行添加操作的时候,CopyOnWriteArrayList底层都是重新copy了一份数组,再往新的数组中添加数组,待添加完了,再将新的数组覆盖原来的数组。

那么既然每次添加元素的时候,都会重新复制一份新的数组,那就带来了一个问题,就是增加了内存的开销。所以,在应用的时候,CopyOnWriteArrayList并不适合做添加操作。但是如果在并发场景下,迭代操作比较频繁,那CopyOnWriteArrayList是个不错的选择。

原文始发于:https://www.jianshu.com/p/552df05d5a24

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> JUC(三)——分段锁机制


 上一篇
面试再问ThreadLocal,别说你不会! 面试再问ThreadLocal,别说你不会!
点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:坚持就是胜利 juejin.im/post/5d427f306fb9a06b122f1b94 ThreadLocal是什么以前面试的时候问到Thre
2021-04-05
下一篇 
通过一个生活中的案例场景,揭开并发包底层AQS的神秘面纱 通过一个生活中的案例场景,揭开并发包底层AQS的神秘面纱
******** 本文导读 生活中案例场景介绍 联想到 AQS 到底是什么 AQS 的设计初衷 揭秘 AQS 底层实现 最后的总结 联想到 AQS 到底是什么 揭秘 AQS 底层实现 当你在学习某一个技能的时候,是否曾
2021-04-05