同步容器与并发容器
Posted _mcj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同步容器与并发容器相关的知识,希望对你有一定的参考价值。
1.同步容器
1.1 什么是同步容器
同步容器是指那些在容器内部已经同步化了,使我们在并发操作使用容器的时候不需要进行手动同步了。
1.2 同步容器的分类
同步容器可以分为两大类:普通类和内部类
普通类
主要是Vector、Stack、HashTable
普通类其实现的方式是通过在方法上添加synchronized关键字来进行实现的。就比如,Vector容器的add,set,get方法,其源码如下:
public synchronized E get(int index)
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
public synchronized E set(int index, E element)
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
public synchronized boolean add(E e)
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
从上面vector的源码可以看出其主要是通过synchronized关键字的方式实现的。
内部类
是指Collections创建的内部类,比如Collections.SynchronizedList、 Collections.SynchronizedSet等
内部类是把原有的容器进行包装(通过this.list = list直接指向需要同步的容器),然后局部加锁,这样一来,就生成了线程安全的类;源码如下:
static class SynchronizedCollection<E> implements Collection<E>, Serializable
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c)
this.c = Objects.requireNonNull(c);
mutex = this;
SynchronizedCollection(Collection<E> c, Object mutex)
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
public int size()
synchronized (mutex) return c.size();
public boolean isEmpty()
synchronized (mutex) return c.isEmpty();
public boolean contains(Object o)
synchronized (mutex) return c.contains(o);
public Object[] toArray()
synchronized (mutex) return c.toArray();
public <T> T[] toArray(T[] a)
synchronized (mutex) return c.toArray(a);
public Iterator<E> iterator()
return c.iterator(); // Must be manually synched by user!
public boolean add(E e)
synchronized (mutex) return c.add(e);
public boolean remove(Object o)
synchronized (mutex) return c.remove(o);
public boolean containsAll(Collection<?> coll)
synchronized (mutex) return c.containsAll(coll);
public boolean addAll(Collection<? extends E> coll)
synchronized (mutex) return c.addAll(coll);
public boolean removeAll(Collection<?> coll)
synchronized (mutex) return c.removeAll(coll);
public boolean retainAll(Collection<?> coll)
synchronized (mutex) return c.retainAll(coll);
public void clear()
synchronized (mutex) c.clear();
public String toString()
synchronized (mutex) return c.toString();
// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> consumer)
synchronized (mutex) c.forEach(consumer);
@Override
public boolean removeIf(Predicate<? super E> filter)
synchronized (mutex) return c.removeIf(filter);
@Override
public Spliterator<E> spliterator()
return c.spliterator(); // Must be manually synched by user!
@Override
public Stream<E> stream()
return c.stream(); // Must be manually synched by user!
@Override
public Stream<E> parallelStream()
return c.parallelStream(); // Must be manually synched by user!
private void writeObject(ObjectOutputStream s) throws IOException
synchronized (mutex) s.defaultWriteObject();
可以看到他是对其对西昂mutex进行加锁,来保证方法的同步。
同步类和内部类的区别
区别 | 普通类 | 内部类 |
---|---|---|
锁的对象 | 不可指定,只能this | 可指定,默认this |
锁的范围 | 方法体(包括迭代) | 代码块(不包括迭代) |
适用范围 | 窄-个别容器 | 广-所有容器 |
1.3 同步容器的优缺点
优点
- 并发编程中,独立操作是线程安全的。比如只是执行一个add,get操作
缺点
- 性能差,基本上每个操作都进行了加锁
- 对于复合操作其还不是性能安全的
2.并发容器
并发容器主要是为了用来改善同步容器的性能的,因为同步容器对于每一步操作都进行了加锁,因此导致了同一时刻只能有一个线程访问容器,所以就导致了性能很差。因此为了提升同步容器的性能就引入了并发容器。其将对共享资源在同一时刻的操作由串行改为了并行。
并发容器主要是通过分段锁,非阻塞的CAS算法等技术来对容器进行优化,主要的容器有ConcurrentHashMap、CopyOnWriteArrayList等。
ConcurrentHashMap
它是采用了分段锁的机制,使锁的粒度更小,允许线程并发的进行操作。
CopyOnWriteArrayList
其可以保证线程安全,保证读线程之间不会被阻塞。其是通过Copy-On-Write(COW)思想来实现的,COW即写时复制的思想,也就是在像一个容器中添加元素时,先进行复制原容器,然后在复制的容器中进行添加数据,添加好之后,再指向复制的容器。
可以通过其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();
其在进行添加元素的时候进行加锁,保证只有一个线程进行数组复制,然后通过复制一个新的列表来进行添加的操作,最后再指向新的列表。进而保证了读写操作不是再同一个数据列表中进行操作的。
从上面的描述我们就可以看出COW会存在内存占用问题。同时其数据一致性也是保证的最终数据一致性。
Java并发机制--同步容器与并发容器
Java并发编程:同步容器
整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3933404.html
1、同步容器出现原因
常用的ArrayList,LinkedList,HashMap,HashSet,Deque等都是线程不安全的;
Java为方便多线程编程,提供了同步容器供用户使用。
2、同步容器类:
2.1:第一类:Vector(ArrayList)、Stack(Vector的子类)、HashTable(HashMap)
2.2:第二类:Collections类中提供的静态工厂方法创建的类。如Collections.synchronizedXxx()获取
3、同步容器的缺陷:
3.1:使用synchronized关键字进行同步降低了效率;
3.2:像Vector这种add和get方法都进行同步,在多线程读时,竞争锁,效率很低。
3.3:多线程操作同步容器时,可能抛出 ConcurrentModificationException异常。
4、ConcurrentModificationException异常原因及解决
4.1:单线程下:Iterator迭代器迭代时,调用了list的remove修改了容器大小,应该调用Iterator的remove方法;
4.2:多线程下:多个线程执行Iterator并修改,此时Iterator线程私有。
改正:【1】获取Iterator时,使用synchronized或者Lock同步。
【2】使用并发容器CopyOnWriteArrayList代替ArrayList和Vector
5、并发容器:java.util.concurrent包
同步容器将所有对容器状态的访问都串行化,保证了安全,却降低了效率;
5.1:ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap()),HashMap同步时锁住所有的段,而ConcurrentHashMap只锁住对应的段(segment);
5.2:CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set,写时复制List和写时复制set;
5.3: 其他
以上是关于同步容器与并发容器的主要内容,如果未能解决你的问题,请参考以下文章