在 Java 中,同步容器主要包括 2 類:java
同步容器的同步原理就是在方法上用 synchronized
修飾。那麼,這些方法每次只容許一個線程調用執行。算法
因爲被 synchronized
修飾的方法,每次只容許一個線程執行,其餘試圖訪問這個方法的線程只能等待。顯然,這種方式比沒有使用 synchronized
的容器性能要差。數組
同步容器真的必定安全嗎?安全
答案是:未必。同步容器未必真的安全。在作複合操做時,仍然須要加鎖來保護。bash
常見覆合操做以下:數據結構
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class Test { static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
</pre>複製代碼
執行時可能會出現數組越界錯誤。併發
Vector 是線程安全的,爲何還會報這個錯?很簡單,對於 Vector,雖然能保證每個時刻只能有一個線程訪問它,可是不排除這種可能:性能
當某個線程在某個時刻執行這句時:學習
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.get(i);
</pre>複製代碼
倘若此時 vector 的 size 方法返回的是 10,i 的值爲 9ui
而後另一個線程執行了這句:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.remove(i);
</pre>複製代碼
將下標爲 9 的元素刪除了。
那麼經過 get 方法訪問下標爲 9 的元素確定就會出問題了。
所以爲了保證線程安全,必須在方法調用端作額外的同步措施,以下面所示:
public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
synchronized (Test.class) {
//進行額外的同步
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
synchronized (Test.class) {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}複製代碼
在對 Vector 等容器併發地進行迭代修改時,會報 ConcurrentModificationException 異常,關於這個異常將會在後續文章中講述。
可是在併發容器中不會出現這個問題。
JDK 的 java.util.concurrent
包(即 juc)中提供了幾個很是有用的併發容器。
ConcurrentHashMap 類在 jdk1.7 中的設計,其基本結構如圖所示:
每個 segment 都是一個 HashEntry<K,V>[] table, table 中的每個元素本質上都是一個 HashEntry 的單向隊列。好比 table[3]爲首節點,table[3]->next 爲節點 1,以後爲節點 2,依次類推。
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable { // 將整個hashmap分紅幾個小的map,每一個segment都是一個鎖;與hashtable相比,這麼設計的目的是對於put, remove等操做,能夠減小併發衝突,對 // 不屬於同一個片斷的節點能夠併發操做,大大提升了性能
final Segment<K,V>[] segments;
// 本質上Segment類就是一個小的hashmap,裏面table數組存儲了各個節點的數據,繼承了ReentrantLock, 能夠做爲互拆鎖使用
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
}
// 基本節點,存儲Key, Value值
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
}
</pre>複製代碼
transient volatile HashEntry<K,V>[] table
保存數據,採用 table 數組元素做爲鎖,從而實現了對每一行數據進行加鎖,進一步減小併發衝突的機率。<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">final V putVal(K key, V value, Boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f;
int n, i, fh;
// 若是table爲空,初始化;不然,根據hash值計算獲得數組索引i,若是tab[i]爲空,直接新建節點Node便可。注:tab[i]實質爲鏈表或者紅黑樹的首節點。
if (tab == null || (n = tab.length) == 0)
tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;
// no lock when adding to empty bin
}
// 若是tab[i]不爲空而且hash值爲MOVED,說明該鏈表正在進行transfer操做,返回擴容完成後的table。 else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); else {
V oldVal = null;
// 針對首個節點進行加鎖操做,而不是segment,進一步減小線程衝突
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 若是在鏈表中找到值爲key的節點e,直接設置e.val = value便可。
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 若是沒有找到值爲key的節點,直接新建Node並加入鏈表便可。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 若是首節點爲TreeBin類型,說明爲紅黑樹結構,執行putTreeVal操做。 else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// 若是節點數>=8,那麼轉換鏈表結構爲紅黑樹結構。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null) return oldVal;
break;
}
}
}
// 計數增長1,有可能觸發transfer操做(擴容)。
addCount(1L, binCount);
return null;
}
</pre>複製代碼
示例
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap 在併發迭代訪問時會拋出 ConcurrentModificationException 異常 // Map<Integer, Character> map = new HashMap<>();
Map<Integer, Character> map = new ConcurrentHashMap<>();
Thread wthread = new Thread(() -> {
System.out.println("寫操做線程開始執行"); for (int i = 0; i < 26; i++) {
map.put(i, (char) ('a' + i));
}
});
Thread rthread = new Thread(() -> {
System.out.println("讀操做線程開始執行"); for (Integer key : map.keySet()) {
System.out.println(key + " - " + map.get(key));
}
});
wthread.start();
rthread.start();
Thread.sleep(1000);
}
}</pre>複製代碼
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
</pre>複製代碼
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public Boolean add(E e) {
//ReentrantLock加鎖,保證線程安全
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();
}
}
</pre>複製代碼
刪除操做
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public E remove(int index) {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0) //若是要刪除的是列表末端數據,拷貝前len-1個數據到新副本上,再切換引用
setArray(Arrays.copyOf(elements, len - 1)); else {
//不然,將除要刪除元素以外的其餘元素拷貝到新副本中,並切換引用
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
finally {
//解鎖
lock.unlock();
}
}
</pre>複製代碼
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
</pre>複製代碼
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
List<String> list;
ReadTask(List<String> list) {
this.list = list;
}
public void run() {
for (String str : list) {
System.out.println(str);
}
}
}
static class WriteTask implements Runnable {
List<String> list;
int index;
WriteTask(List<String> list, int index) {
this.list = list;
this.index = index;
}
public void run() {
list.remove(index);
list.add(index, "write_" + index);
}
}
public void run() {
final int NUM = 10;
// ArrayList 在併發迭代訪問時會拋出 ConcurrentModificationException 異常 // List<String> list = new ArrayList<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < NUM; i++) {
list.add("main_" + i);
}
ExecutorService executorService = Executors.newFixedThreadPool(NUM);
for (int i = 0; i < NUM; i++) {
executorService.execute(new ReadTask(list));
executorService.execute(new WriteTask(list, i));
}
executorService.shutdown();
}
public static void main(String[] args) {
new CopyOnWriteArrayListDemo().run();
}
}
</pre>複製代碼
歡迎你們掃下方二維碼加java學習技術交流羣,一塊兒夯實基礎,提高自我價值