同步容器类
Posted dazhu123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同步容器类相关的知识,希望对你有一定的参考价值。
1:同步容器类
1.1:Hashtable简单说明
public class Main { static Hashtable ht = new Hashtable(); public static void main(String[] args){ ht.put(0,0); ht.put(1,1); ht.put(2,2); ht.put(3,3); ht.put(4,4); ht.put(5,5); ht.put(6,6); ht.put(7,7); new Thread(new Runnable() { public void run() { getLast(ht); } },"线程1").start(); new Thread(new Runnable() { public void run() { deleteLast(ht); } },"线程2").start(); } public static void getLast(Hashtable ht){ synchronized (ht){ int lastIndex = ht.size()-1; System.out.println("线程名:"+Thread.currentThread().getName()+" "+"getLast:"+ht.get(lastIndex)); } } public static void deleteLast(Hashtable ht){ synchronized (ht){ int lastIndex = ht.size()-1; System.out.println("线程名:"+Thread.currentThread().getName()+" "+"deleteLast:"+ht.remove(lastIndex)); } } }
上述代码中定义了一个共有的线程安全类Hashtable,由线程1和线程2进行操作,其中;
- 线程1,获得Hashtable的最后一个元素。
- 线程2,删除Hashtable的最后一个元素。
虽然Hashtable内部的很多方法都是同步方法,但是由于我们自己定义的方法使用Hashtable内部方法组合乘一个复合操作。如果我们不对ht加锁,由于亮哥线程有可能在都进入获得了size,但是先执行了remove的方法,所以导致在获得Hashtable中最后一个元素时,有可能会出现nullpointer的情况。当我们给每一个复合操作都加上锁后,就不会出行这个不想要的bug。
1.2:Hashtable与迭代器
Interator迭代器是容器类的常用的遍历方式,在很多容器中使用该方法,同时该方法并没有对容器加锁。如果在使用容器类中的Interator过程中,有其他线程修改了容器,则会触发fail-fast机制,这种机制,一旦在迭代容器过程中,修改了容器内容,则会抛出ConcurentModificationException异常。我们看下面Hashtable的源码分析。
public synchronized void forEach(BiConsumer<? super K, ? super V> action) {//该方法是同步方法!!!! Objects.requireNonNull(action); // explicit check required in case // table is empty. final int expectedModCount = modCount;//不可变的expectedMoDcout Entry<?, ?>[] tab = table; for (Entry<?, ?> entry : tab) { while (entry != null) { action.accept((K)entry.key, (V)entry.value); entry = entry.next; if (expectedModCount != modCount) {//如果在迭代过程中,发现两个值不相等,则会抛出异常, throw new ConcurrentModificationException(); } } } }
由于Hashtable是线程安全的,内部迭代器是同步的。在其他线程不安全容器类中,迭代器并没有同步。所以一旦再多线程系统中,很容器触发ConcurrentModificationException。
当然,如果不希望在遍历的过程中,对容器上锁,可以通过克隆的方法,在每一个线程中都会容器进行克隆一次,相当于以后都在线程封闭的内部的容器进行操作,就不会产生问题了!
2:并发容器
在上面介绍的同步容器类中,都是通过对容器状态进行串行化访问,即同步的访问,以实现线程安全行,但是这种方式的代价就是会严重降低程序并发性,线程之间竞争时,吞吐率会大大下降。在新增的并发容器类中,有ConcurrentHashMap来替代散列的基于array的Map,同时增加CopyOnWriteArrayList接口,用于在遍历操作为主要操作的场景下的,替代同步的List,通过新的ConcurrentMap接口中没增加的很多同步的复合操作如“若没有则添加”以及“若有则删除”。
java5.0增加了,两种容器类型Queue和BlockingQueue。BlockingQueue增加了可阻塞的插入和获取的操作,如果队列为空,那么获得元素的线程将一直被阻塞,直到队列中出现一个可用的元素。如果队列已满,则阻塞插入元素的线程,直到队列出现的可用的空间。
2.1:ConcurrentHashMap
以HashMap为例,在其中HashMap.get和contains方法中,都可能包含大量的操作,比如equals方法进行比较,这将包含大量的计算量,同时,由于糟糕的散列函数HashCode方法,会极端时Map变成List,这样的操作都加了锁后,这会花费很多的时间。相比于HashMap,ConcurrentHashMap,使用不同的加锁策略来提高并发性和伸缩性。在ConcurrentHashMap中,不是像其他方法一样,在对容器上锁,而使每一次只有一个线程访问容器,而是采用粒度更细的加锁机制来获得更大程度的共享,这种机制被称为分段锁。在这种机制中,大量的读取线程可以并发访问Map,而不会相互影响!但是当应用程序需要对Map加锁,以独占访问的时候比如:通过原子操作添加一个映射,或者对Map迭代器迭代若干次并保证元素顺序相同要对整个Map加锁,才应该放弃ConcurrentMap。
@并发性的目的,就是提高多线程访问Mapd的正确性!一般的操作可以通过上锁,但是上锁在一些耗时的方法中,很影响性能,或者通过克隆变量,或ThreadLocal类,还有一种使用并发容器来提高并发性。
3:阻塞队列
以LinkedBlockingQueue为例子,进行介绍阻塞队列的特点和如何运用阻塞队列来进行设计生产和消费模式?
以上是关于同步容器类的主要内容,如果未能解决你的问题,请参考以下文章
[工作积累] UE4 并行渲染的同步 - Sync between FParallelCommandListSet & FRHICommandListImmediate calls(代码片段