阿里P7教你巧用Java的同步容器和并发容器

发布时间:2020-06-19 06:34:56 作者:Java_老男孩
来源:网络 阅读:325

同步容器

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

同步容器的缺陷

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

性能问题

由于被 synchronized 修饰的方法,每次只允许一个线程执行,其他试图访问这个方法的线程只能等待。显然,这种方式比没有使用 synchronized 的容器性能要差。

安全问题

同步容器真的一定安全吗?

答案是:未必。同步容器未必真的安全。在做复合操作时,仍然需要加锁来保护。

常见复合操作如下:

不安全的示例
<pre Courier New" !important; font-size: 12px !important;">public class Test { 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)   {
        }
    }
}
}
</pre>

执行时可能会出现数组越界错误。

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

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

<pre Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
    vector.get(i);
</pre>

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

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

<pre Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
    vector.remove(i);
</pre>

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

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

安全示例

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

public class Test {
    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 (Test.class) {
                        //进行额外的同步
                        for (int i=0;i<vector.size();i++)
                                                    vector.remove(i);
                    }
                }
                ;
            }
            ;
            Thread thread2 = new Thread(){
                public void run() {
                    synchronized (Test.class) {
                        for (int i=0;i<vector.size();i++)
                                                    vector.get(i);
                    }
                }
                ;
            }
            ;
            thread1.start();
            thread2.start();
            while(Thread.activeCount()>10)   {
            }
        }
    }
}
ConcurrentModificationException 异常

在对 Vector 等容器并发地进行迭代修改时,会报 ConcurrentModificationException 异常,关于这个异常将会在后续文章中讲述。

但是在并发容器中不会出现这个问题。

并发容器

JDK 的 java.util.concurrent 包(即 juc)中提供了几个非常有用的并发容器。

ConcurrentHashMap

要点
源码
JDK7

ConcurrentHashMap 类在 jdk1.7 中的设计,其基本结构如图所示:

阿里P7教你巧用Java的同步容器和并发容器

每一个 segment 都是一个 HashEntry<K,V>[] table, table 中的每一个元素本质上都是一个 HashEntry 的单向队列。比如 table[3]为首节点,table[3]->next 为节点 1,之后为节点 2,依次类推。

<pre Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable { // 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对 // 不属于同一个片段的节点可以并发操作,大大提高了性能
final Segment<K,V>[] segments;
// 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用
static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile HashEntry<K,V>[] table;
    transient int count;
}
// 基本节点,存储Key, Value值
static final class HashEntry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;
}
}
</pre>
JDK8

示例

<pre Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap 在并发迭代访问时会抛出 ConcurrentModificationException 异常 // Map<Integer, Character> map = new HashMap<>();
        Map<Integer, Character> map = new ConcurrentHashMap<>();

        Thread wthread = new Thread(() -> {
            System.out.println("写操作线程开始执行"); for (int i = 0; i < 26; i++) {
                map.put(i, (char) ('a' + i));
            }
        });
        Thread rthread = new Thread(() -> {
            System.out.println("读操作线程开始执行"); for (Integer key : map.keySet()) {
                System.out.println(key + " - " + map.get(key));
            }
        });
        wthread.start();
        rthread.start();
        Thread.sleep(1000);
    }
}</pre>

CopyOnWriteArrayList

要点

阿里P7教你巧用Java的同步容器和并发容器

源码
重要属性
<pre Courier New" !important; font-size: 12px !important;">public E remove(int index) {
    //加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0) //如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
        setArray(Arrays.copyOf(elements, len - 1)); else {
            //否则,将除要删除元素之外的其他元素拷贝到新副本中,并切换引用
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                                          numMoved);
            setArray(newElements);
        }
        return oldValue;
    }
    finally {
        //解锁
        lock.unlock();
    }
}
</pre>
示例
<pre Courier New" !important; font-size: 12px !important;">public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
        List<String> list;
ReadTask(List<String> list) {
    this.list = list;
}
public void run() {
    for (String str : list) {
        System.out.println(str);
    }
}
}
static class WriteTask implements Runnable {
List<String> list;
int index;
WriteTask(List<String> list, int index) {
    this.list = list;
    this.index = index;
}
public void run() {
    list.remove(index);
    list.add(index, "write_" + index);
}
}
public void run() {
final int NUM = 10;
// ArrayList 在并发迭代访问时会抛出 ConcurrentModificationException 异常 // List<String> list = new ArrayList<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < NUM; i++) {
    list.add("main_" + i);
}
ExecutorService executorService = Executors.newFixedThreadPool(NUM);
for (int i = 0; i < NUM; i++) {
    executorService.execute(new ReadTask(list));
    executorService.execute(new WriteTask(list, i));
}
executorService.shutdown();
}
public static void main(String[] args) {
new CopyOnWriteArrayListDemo().run();
}
}
</pre>

文末彩蛋

针对于上面所涉及到的知识点我总结出了有1到5年开发经验的程序员在面试中涉及到的绝大部分架构面试题及答案做成了文档和架构视频资料免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以关注我一下以后会有更多干货分享。

资料获取方式 QQ群搜索“708-701-457” 即可免费领取

阿里P7教你巧用Java的同步容器和并发容器
阿里P7教你巧用Java的同步容器和并发容器
阿里P7教你巧用Java的同步容器和并发容器

推荐阅读:
  1. 并发容器之ConcurrentLinkedQueue
  2. 如何在Java中实现同步容器和并发容器

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java 同步容器 并发容器

上一篇:mongodb批量查询库中表的统计信息

下一篇:NFS网络文件共享存储服务器

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》