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会存在内存占用问题。同时其数据一致性也是保证的最终数据一致性。