您的位置:首页 > 其它

线程(集合的线程安全问题)

2018-03-29 20:15 295 查看
集合与线程安全
iterator

CAS算法

集合与线程安全

Do you notice that all the basic collection classes - ArrayList, LinkedList, HashMap, HashSet, TreeMap, TreeSet, etc - all are not synchronized? In fact, all collection classes (except Vector and Hashtable) in the java.util package are not thread-safe. The only two legacy collections are thread-safe: Vector and Hashtable.

大多数的集合类都不是线程安全的,除了早期设计的Vector和Hashtable外,因为加锁是昂贵的代价。

it’s always wise to use thread-safe collections instead of writing synchronization code manually.

要保证及集合的线程安全,可以手动使用sychronized,但更推荐使用线程安全的集合

有工厂方法去创建这些集合:
Collections.synchronizedXXX(collection)


但要注意的是,

when using the iterator of a synchronized collection we should use synchronized block to safeguard the iteration code because the iterator itself is not thread-safe.

当对同步集合使用迭代器时,我们应该使用同步代码块去保障迭代器的线性安全,因为迭代器本身并不是线程安全的,就像下面这样:

e.g.

synchronized (safeList) {
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
}


Also note that the iterators of the synchronized collections are fail-fast.

对于同步集合的迭代器都是fail-fast类型的。

关于迭代器,有好几种类型,下面的一节就介绍了不同的迭代器。

上面的叫
synchronized wrappers
,但它们还是有缺点:

their synchronization mechanism uses the collection object itself as the lock object. That means when a thread is iterating over elements in a collection, all other collection’s method block, causing other threads having to wait.

它们的同步机制都是用集合本身作为锁,这就意味着当一个线程对一个集合里的元素进行便利,那么该集合的所有其他方法都将阻塞,导致其他的线程需要等待。

于是,有了更优秀的改进,就是
Concurrent Collections


它可以分为三大类:

1. copy-on-write collections

2. Compare-And-Swap collections(后面的小节有详细解释)

3. Using a special lock object

注意:

1、针对写时复制的集合

1) 它store values in an immutable array(在不可变数组中存值)

2) 所有add操作加锁

3) copy-on-write collections have snapshot iterators which do not throw “ConcurrentModificationException”

2、

Note that the CAS collections have weakly consistent iterators, which reflect some but not necessarily all of the changes that have been made to their backing collection since they were created. Weakly consistent iterators do not throw ConcurrentModificationException.

3、

也是具有读不一致的缺点,利用了分段锁的思想

iterator

Iterator role in Multithreading

Iterator are used to iterate over the collection of objects and provide the their references. It is important to understand the behavior of iterator when some other thread does the modification on the object (or Concurrent modification) which is the part of collection and being iterated over by this thread. This kind of concurrent modification may leave the impact of dirty read, phantom read, etc. Collections based on their concurrent, synchronize and non synchronize behavior, it came up different kinds of iterators.

Fail-fast iterators

Collection iterator are used to traverse elements of a collection. Fail-fast iterator throws “ConcurrentModificationException” while iterating through the collection, if at the same time another thread does the modification. However, it doesn’t mean that it saves you from the arbitratory behavior of collection. Because, as per Oracle docs, Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. It is not recommended to write program that bases the “ConcurrentModificationException”.

The following test program mimics a situation that throws
ConcurrentModificationException
:

import java.util.*;

/**
* This test program illustrates how a collection's iterator fails fast
* and throw ConcurrentModificationException
* @author www.codejava.net
*
*/
public class IteratorFailFastTest {

private List<Integer> list = new ArrayList<>();

public IteratorFailFastTest() {
for (int i = 0; i < 10_000; i++) {
list.add(i);
}
}

public void runUpdateThread() {
Thread thread1 = new Thread(new Runnable() {

public void run() {
for (int i = 10_000; i < 20_000; i++) {
list.add(i);
}
}
});

thread1.start();
}

public void runIteratorThread() {
Thread thread2 = new Thread(new Runnable() {

public void run() {
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
Integer number = iterator.next();
System.out.println(number);
}
}
});

thread2.start();
}

public static void main(String[] args) {
IteratorFailFastTest tester = new IteratorFailFastTest();

tester.runIteratorThread();
tester.runUpdateThread();
}
}


Snapshot iterators

Snapshot iterator makes copy of the internal data structure (object collection) and iterates over the copied data structure. Any structural modification done to the iterator affects the copied data structure. So, original data structure remains structurally unchanged .Hence, no “ConcurrentModificationException” throws by the snapshot iterator. Snapshot iterators are fail-safe iterator. Copy-on-write collections use Snapshot iterators to iterate over the elements.

weakly-consistent iterators

Weakly-consistent iterators reflect some but not necessarily all of the changes that have been made to their backing collection since they were created. For example, if elements in the collection have been modified or removed before the iterator reaches them, it definitely will reflect these changes, but no such guarantee is made for insertions. Collections which rely on CAS(compare-and-swap) have weakly consistent iterators.

Undefined iterators

The results of modifying a collection during iteration are undefined and may result in inconsistencies. The examples here are the legacy collections Vector and Hashtable and their methods that return Enumeration, including Vector.elements, Hashtable.elements, and Hashtable.keys.

Fail-safe iterator is a myth

Java specification does not use the term “Thread safe” anywhere. However, Snapshot iterator and Weakly-consistent iterators can be considered as fail safe iterator.

CAS算法

compare and swap

前言:加锁的方式一般分为“乐观锁”和“悲观锁”,而CAS就属于乐观锁的一种。我们常见的synchronized就是悲观锁。

【原网站佛系翻译】

One of the best additions in java 5 was Atomic operations supported in classes such as
AtomicInteger
,
AtomicLong
etc. These classes help you in minimizing the need of complex (un-necessary) multi-threading code for some basic operations such as increment or decrement a value which is shared among multiple threads. These classes internally rely on an algorithm named CAS (compare and swap). In this article, I am going to discuss this concept in detail.

java5新增的原子操作,能使多线程中,基本的增值、减值操作的开销最小化(前提是这个值是被多个线程共享,有线程安全问题的)。实现这些技术的类,实际上就是利用了CAS算法。

Traditional locking mechanisms, e.g. using synchronized keyword in java, is said to be pessimistic technique of locking or multi-threading. It asks you to first guarantee that no other thread will interfere in between certain operation (i.e. lock the object), and then only allow you access to any instance/method.

It’s much like saying “please close the door first; otherwise some other crook will come in and rearrange your stuff”.

传统的加锁机制,例如java中的synchronized关键字,就是一种悲观的加锁技术。因为它首先要求你保证其他的线程不会干扰目前的操作,才让你进入这个方法(或者说才让你拿到这个实例)

(用形象的话就是:)“请先关上门,否则骗子会进来捣乱你的事情”

Though above approach is safe and it does work, but it put a significant penalty on your application in terms of performance. Reason is simple that waiting threads can not do anything unless they also get a chance and perform the guarded operation.

尽管上面的方法是安全并且是能够工作的,但是对于应用的性能造成了很大的伤害。原因很简单,正在等待的线程不能做任何事除非它们得到了机会,并执行了被保护的操作。

There exist one more approach which is more efficient in performance, and it optimistic in nature. In this approach, you proceed with an update, being hopeful that you can complete it without interference. This approach relies on collision detection to determine if there has been interference from other parties during the update, in which case the operation fails and can be retried (or not).

存在其他的在性能上更优的方法,(这种方法)实际上是乐观的。这种方法里,你执行某个更新,并希望在完成之前不受打扰。这种方式依赖冲突检测,冲突检测用来确定在执行更新的操作过程中,是否被其他实体打扰,如果被打扰了,那么操作就失败,并重试。

The optimistic approach is like the old saying, “It is easier to obtain forgiveness than permission”, where “easier” here means “more efficient”.

这种乐观的方式就像一句古话所说,“宽容比允许更加容易”,这儿的容易指的是“更有效率”。

Compare and Swap is a good example of such optimistic approach, which we are going to discuss next.

CAS算法就是一种乐观法的好例子,下面我们会继续讨论。

Compare and Swap Algorithm

This algorithm compares the contents of a memory location to a given value and, only if they are the same, modifies the contents of that memory location to a given new value. This is done as a single atomic operation.

这种算法将某个内存地址的值与给定的值做比较,如果是一样的,那么就会将这个内存地址的值更新。这是作为单个原子操作完成的。

The atomicity guarantees that the new value is calculated based on up-to-date information; if the value had been updated by another thread in the meantime, the write would fail.

原子性保证了新值是基于最新的信息计算的,如果同时这个值被另外的线程更新,那么写操作就会失败。

The result of the operation must indicate whether it performed the substitution; this can be done either with a simple Boolean response (this variant is often called compare-and-set), or by returning the value read from the memory location (not the value written to it).

操作的寄过必须指明它是否执行了置换操作,这能通过简单的布尔值反应,或者通过返回内存地址的值。

There are 3 parameters for a CAS operation:

cas操作里有三个参数

1. A memory location V where value has to be replaced

将被替换的内存地址V

2. Old value A which was read by thread last time

上一次被内存读到的旧值A

3. New value B which should be written over V

将写入地址V的新值B

Let’s understand thw whole process with an example. Assume V is a memory location where value “10” is stored. There are multiple threads who want to increment this value and use the incremented value for other operations, a very practical scenario. Let’s break the whole CAS operation in steps:

下面我们用一个例子去讨论整个过程,假设V地址的值为10。有多个线程想去增加这个值,并用增加后的值去执行别的操作。让我们把整个操作一步步分解。

1) Thread 1 and 2 want to increment it, they both read the value and increment it to 11.

线程1,2都读到了这个值,并想要增加这个值到11

V = 10, A = 0, B = 0


2) Now thread 1 comes first and compare V with it’s last read value:

线程1首先完成,并在更新前进行比较,此时V的值是否与上次读到的值(10)相同

V = 10, A = 10, B = 11


if     A = V
V = B
else
operation failed
return V


Clearly the value of V will be overwritten as 11, i.e. operation was successful.

此时比较结果当然是相同的,因而V的值能增加,操作成功。

3) Thread 2 comes and try the same operation as thread 1

接下来,线程2也完成了操作,但在更新前比较,发现如今V的值为11,不等于之前的10,因此操作不成功。

V = 11, A = 10, B = 11


if     A = V
V = B
else
operation failed
return V


4) In this case, V is not equal to A, so value is not replaced and current value of V i.e. 11 is returned. Now thread 2, again retry this operation with values:

这种情况下,线程2重新进行操作,第二次操作前再次读最新的值,为11,操作完成后做比较,值未变,因此更新成功。

V = 11, A = 11, B = 12


And this time, condition is met and incremented value 12 is returned to thread 2.

In summary, when multiple threads attempt to update the same variable simultaneously using CAS, one wins and updates the variable’s value, and the rest lose. But the losers are not punished by suspension of thread. They are free to retry the operation or simply do nothing.

Thats all for this simple but important concept related to atomic operations supported in java.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: