您的位置:首页 > 编程语言 > Java开发

Java并发编程(一)之基础知识(基础构建模块)

2017-12-31 21:53 253 查看

欢迎参考Java并发编程(前言)之并发性标注与基本概念

此文章只是给自己做一个小抄,如有冒犯,还忘见谅

1. 同步容器类

同步容器类包括Vector和Hashtable,是早期JDK的一部分,这些类实现的方法是:将它们的状态封装起来,并对每个共有的方法进行同步,使得每个线程只有一个线程能访问它们。

同步容器类的问题

同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作。复合类操作如:迭代(反复访问元素,遍历完容器中的所有元素)、跳转(根据指定的顺序找到当前元素的下一个元素)、以及条件运算。这些复合操作在多线程并发的修改容器时,可能会表现出意外的行为。

// Vector 上可能导致混乱结果的复合操作
public static Object getLast(Vector list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}

public static void deleteLast(Vector list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}


//在使用客户端加锁的 Vector 的复合操作
public static Object getLast(Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}

public static void deleteLast(Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}


//可能会抛出 ArrayIndexOutOfBoundsException 的迭代操作
for(int i = 0; i < vector.size(); i++){
doSomething(vector.get(i));
}


单线程中,这是成立的,但是在多线程中,有其他的线程并发的修改vector,则可能会导致麻烦,假如在对vector进行迭代时,另一个线程删除了一个元素,则可能会抛出ArrayIndexOutOfBoundsException异常。但是我们可以通过客户端加锁的机制来解决该问题,如下:

synchronized (vector) {
for(int i = 0; i < vector.size(); i++){
doSomething(vector.get(i));
}
}


迭代器与ConcurrentModificationException

当容器在迭代过程中被修改,就会抛出ConcurrentModificationException异常。如果想在迭代过程中,不被其他线程修改容器,还得加锁,或者克隆一个副本出来,在副本上进行迭代,这样就避免了抛出ConcurrentModificationException异常。

隐藏迭代器

虽然加锁可以防止迭代器抛出ConcurrentModificationException异常,但你必须要记住在所有的对所有共享容器进行迭代的地方都需要加锁。因为在某些情况下,迭代器有隐藏起来,比如我们在输出一个容器的时候,会间接的调用toString()方法,标准容器的toString方法将对容器进行迭代。容器的hashCode,equals,containsAll等方法,都会对容器进行迭代,这些间接的迭代都可能会抛出ConcurrentModificationException异常。

如果状态与保护它的同步代码之间相隔越远,那么开发人员就越容易忘记在访问状态时使用正确的同步。

正如封装对象的状态有助于维持不变性条件一样,封装对象的同步机制同样有助于确保实施同步策略。

2. 并发容器

同步容器将所有对容器状态的访问都串行化来实现它们的安全性。这中方法的代价是严重降低并发性,当多个容器竞争容器的锁时,吞吐量将严重降低。

并发容器是针对多个线程并发访问设计的,通过并发容器代替同步容器,可以极大的提高伸缩性并降低风险。

java5.0之后提供了多种并发容器来改善同步容器的性能,如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListMap等

ConcurrentHashMap

ConcurrentHashMap并不是将每个方法都在同一个锁上同步并使得每次只能一个线程访问容器,而是使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制被称为分段锁(Lock Striping),这种加锁机制带来的结果是,在并发访问的环境下将实现更高的吞吐量,在单线程上只损失非常小的性能。

ConcurrentHashMap 返回的迭代器具有弱一致性 ,而并非“及时失败”。弱一致性的迭代器可以容忍并发的修改。

参考: ConcurrentHashMap的原理

CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

“写入时复制(Copy-On-Write)”容器的线程安全性在于,只要正确的发布一个事实不可变的对象,那么在访问该对象时就不再需要进一步的同步。在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性。

仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器。

3. 阻塞队列和生产者-消费者模式

阻塞队列提供了可阻塞的 put 和 take 方法,以及支持定时的 offer 和 poll 方法。如果队列已满,则 put 方法将阻塞直到有可用空间为止;如果队列为空,则 take 方法将会阻塞直到有元素可用;队列提供的 offer 方法,如果数据项不能被添加到队列中,那么将返回一个失败状态。

在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具:它们能抑制并防止产生过多的工作项,使应用程序在符合过载的情况下变得更加健壮。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 并发