在 Java 中,同步容器主要包括 2 類:
成都創(chuàng)新互聯(lián)專注于企業(yè)全網(wǎng)營銷推廣、網(wǎng)站重做改版、三門網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5網(wǎng)站設(shè)計、成都做商城網(wǎng)站、集團公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為三門等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
同步容器的同步原理就是在方法上用?synchronized
?修飾。那么,這些方法每次只允許一個線程調(diào)用執(zhí)行。
由于被?synchronized
?修飾的方法,每次只允許一個線程執(zhí)行,其他試圖訪問這個方法的線程只能等待。顯然,這種方式比沒有使用?synchronized
?的容器性能要差。
同步容器真的一定安全嗎?
答案是:未必。同步容器未必真的安全。在做復(fù)合操作時,仍然需要加鎖來保護。
常見復(fù)合操作如下:
public class Test { static Vector vector = new Vector();
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;i10) {
}
}
}
}
執(zhí)行時可能會出現(xiàn)數(shù)組越界錯誤。
Vector 是線程安全的,為什么還會報這個錯?很簡單,對于 Vector,雖然能保證每一個時刻只能有一個線程訪問它,但是不排除這種可能:
當某個線程在某個時刻執(zhí)行這句時:
for (int i=0;i
假若此時 vector 的 size 方法返回的是 10,i 的值為 9
然后另外一個線程執(zhí)行了這句:
for (int i=0;i
將下標為 9 的元素刪除了。
那么通過 get 方法訪問下標為 9 的元素肯定就會出問題了。
因此為了保證線程安全,必須在方法調(diào)用端做額外的同步措施,如下面所示:
public class Test {
static Vector vector = new Vector();
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;i10) {
}
}
}
}
在對 Vector 等容器并發(fā)地進行迭代修改時,會報 ConcurrentModificationException 異常,關(guān)于這個異常將會在后續(xù)文章中講述。
但是在并發(fā)容器中不會出現(xiàn)這個問題。
JDK 的?java.util.concurrent
?包(即 juc)中提供了幾個非常有用的并發(fā)容器。
ConcurrentHashMap 類在 jdk1.7 中的設(shè)計,其基本結(jié)構(gòu)如圖所示:
每一個 segment 都是一個 HashEntry
public class ConcurrentHashMap extends AbstractMap
implements ConcurrentMap, Serializable { // 將整個hashmap分成幾個小的map,每個segment都是一個鎖;與hashtable相比,這么設(shè)計的目的是對于put, remove等操作,可以減少并發(fā)沖突,對 // 不屬于同一個片段的節(jié)點可以并發(fā)操作,大大提高了性能
final Segment[] segments;
// 本質(zhì)上Segment類就是一個小的hashmap,里面table數(shù)組存儲了各個節(jié)點的數(shù)據(jù),繼承了ReentrantLock, 可以作為互拆鎖使用
static final class Segment extends ReentrantLock implements Serializable {
transient volatile HashEntry[] table;
transient int count;
}
// 基本節(jié)點,存儲Key, Value值
static final class HashEntry {
final int hash;
final K key;
volatile V value;
volatile HashEntry next;
}
}
transient volatile HashEntry<K,V>[] table
?保存數(shù)據(jù),采用 table 數(shù)組元素作為鎖,從而實現(xiàn)了對每一行數(shù)據(jù)進行加鎖,進一步減少并發(fā)沖突的概率。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[] tab = table;;) {
Node f;
int n, i, fh;
// 如果table為空,初始化;否則,根據(jù)hash值計算得到數(shù)組索引i,如果tab[i]為空,直接新建節(jié)點Node即可。注:tab[i]實質(zhì)為鏈表或者紅黑樹的首節(jié)點。
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(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;
// 針對首個節(jié)點進行加鎖操作,而不是segment,進一步減少線程沖突
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node e = f;; ++binCount) {
K ek;
// 如果在鏈表中找到值為key的節(jié)點e,直接設(shè)置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的節(jié)點,直接新建Node并加入鏈表即可。
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key,
value, null);
break;
}
}
}
// 如果首節(jié)點為TreeBin類型,說明為紅黑樹結(jié)構(gòu),執(zhí)行putTreeVal操作。 else if (f instanceof TreeBin) {
Node p;
binCount = 2;
if ((p = ((TreeBin)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// 如果節(jié)點數(shù)>=8,那么轉(zhuǎn)換鏈表結(jié)構(gòu)為紅黑樹結(jié)構(gòu)。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null) return oldVal;
break;
}
}
}
// 計數(shù)增加1,有可能觸發(fā)transfer操作(擴容)。
addCount(1L, binCount);
return null;
}
示例
public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap 在并發(fā)迭代訪問時會拋出 ConcurrentModificationException 異常 // Map map = new HashMap<>();
Map map = new ConcurrentHashMap<>();
Thread wthread = new Thread(() -> {
System.out.println("寫操作線程開始執(zhí)行"); for (int i = 0; i < 26; i++) {
map.put(i, (char) ('a' + i));
}
});
Thread rthread = new Thread(() -> {
System.out.println("讀操作線程開始執(zhí)行"); for (Integer key : map.keySet()) {
System.out.println(key + " - " + map.get(key));
}
});
wthread.start();
rthread.start();
Thread.sleep(1000);
}
}
array - 對象數(shù)組,用于存放元素
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
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);
//在新副本上執(zhí)行添加操作
newElements[len] = e;
//將原容器引用指向新副本
setArray(newElements);
return true;
}
finally {
//解鎖
lock.unlock();
}
}
刪除操作
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) //如果要刪除的是列表末端數(shù)據(jù),拷貝前l(fā)en-1個數(shù)據(jù)到新副本上,再切換引用
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();
}
}
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
List list;
ReadTask(List list) {
this.list = list;
}
public void run() {
for (String str : list) {
System.out.println(str);
}
}
}
static class WriteTask implements Runnable {
List list;
int index;
WriteTask(List 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 在并發(fā)迭代訪問時會拋出 ConcurrentModificationException 異常 // List list = new ArrayList<>();
CopyOnWriteArrayList 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();
}
}
針對于上面所涉及到的知識點我總結(jié)出了有1到5年開發(fā)經(jīng)驗的程序員在面試中涉及到的絕大部分架構(gòu)面試題及答案做成了文檔和架構(gòu)視頻資料免費分享給大家(包括Dubbo、redis、Netty、zookeeper、Spring cloud、分布式、高并發(fā)等架構(gòu)技術(shù)資料),希望能幫助到您面試前的復(fù)習(xí)且找到一個好的工作,也節(jié)省大家在網(wǎng)上搜索資料的時間來學(xué)習(xí),也可以關(guān)注我一下以后會有更多干貨分享。