欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java并發(fā)編程之同步容器

 更新時間:2021年05月07日 16:01:54   作者:湯圓學(xué)Java  
這篇文章主要介紹了Java并發(fā)編程之同步容器,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下

簡介

同步容器主要分兩類,一種是Vector這樣的普通類,一種是通過Collections的工廠方法創(chuàng)建的內(nèi)部類

雖然很多人都對同步容器的性能低有偏見,但它也不是一無是處,在這里我們插播一條阿里巴巴的開發(fā)手冊規(guī)范:

高并發(fā)時,同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能鎖區(qū)塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。

可以看到,只有在高并發(fā)才會考慮到鎖的性能問題,所以在一些小而全的系統(tǒng)中,同步容器還是有用武之地的(當(dāng)然也可以考慮并發(fā)容器,后面章節(jié)再討論)

一、什么是同步容器

定義:就是把容器類同步化,這樣我們在并發(fā)中使用容器時,就不用手動同步,因為內(nèi)部已經(jīng)自動同步了

例子:比如Vector就是一個同步容器類,它的同步化就是把內(nèi)部的所有方法都上鎖(有的重載方法沒上鎖,但是最終調(diào)用的方法還是有鎖的)

源碼:Vector.add

// 通過synchronized為add方法上鎖
public synchronized boolean add(E e) {
  modCount++;
  ensureCapacityHelper(elementCount + 1);
  elementData[elementCount++] = e;
  return true;
}

同步容器主要分兩類:

1.普通類:Vector、Stack、HashTable

2.內(nèi)部類:Collections創(chuàng)建的內(nèi)部類,比如Collections.SynchronizedList、 Collections.SynchronizedSet等

那這兩種有沒有區(qū)別呢?

當(dāng)然是有的,剛開始的時候(Java1.0)只有第一種同步容器(Vector等)

但是因為Vector這種類太局氣了,它就想著把所有的東西都弄過來自己搞(Vector通過toArray轉(zhuǎn)為己有,HashTable通過putAll轉(zhuǎn)為己有);

源碼:Vector構(gòu)造函數(shù)

public Vector(Collection<? extends E> c) {
	// 這里通過toArray將傳來的集合 轉(zhuǎn)為己有
  elementData = c.toArray();
  elementCount = elementData.length;
  // c.toArray might (incorrectly) not return Object[] (see 6260652)
  if (elementData.getClass() != Object[].class)
    elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

所以就有了第二種同步容器類(通過工廠方法創(chuàng)建的內(nèi)部容器類),它就比較聰明了,它只是把原有的容器進(jìn)行包裝(通過this.list = list直接指向需要同步的容器),然后局部加鎖,這樣一來,即生成了線程安全的類,又不用太費力;

源碼:Collections.SynchronizedList構(gòu)造函數(shù)

SynchronizedList(List<E> list) {
  super(list);
  // 這里只是指向傳來的list,不轉(zhuǎn)為己有,后面的相關(guān)操作還是基于原有的list集合
  this.list = list;
}

他們之間的區(qū)別如下:

兩種同步容器的區(qū)別 普通類 內(nèi)部類
鎖的對象 不可指定,只能this 可指定,默認(rèn)this
鎖的范圍 方法體(包括迭代) 代碼塊(不包括迭代)
適用范圍 窄-個別容器 廣-所有容器

這里我們重點說下鎖的對象:

  • 普通類鎖的是當(dāng)前對象this(鎖在方法上,默認(rèn)this對象);
  • 內(nèi)部類鎖的是mutex屬性,這個屬性默認(rèn)是this,但是可以通過構(gòu)造函數(shù)(或工廠方法)來指定鎖的對象

源碼:Collections.SynchronizedCollection構(gòu)造函數(shù)

final Collection<E> c;  // Backing Collection
// 這個就是鎖的對象
final Object mutex;     // Object on which to synchronize

SynchronizedCollection(Collection<E> c) {
  this.c = Objects.requireNonNull(c);
// 初始化為 this
  mutex = this;
}

SynchronizedCollection(Collection<E> c, Object mutex) {
  this.c = Objects.requireNonNull(c);
  this.mutex = Objects.requireNonNull(mutex);
}

這里要注意一點就是,內(nèi)部類的迭代器沒有同步(Vector的迭代器有同步),需要手動加鎖來同步

源碼:Vector.Itr.next 迭代方法(有上鎖)

public E next() {
  synchronized (Vector.this) {
    checkForComodification();
    int i = cursor;
    if (i >= elementCount)
      throw new NoSuchElementException();
    cursor = i + 1;
    return elementData(lastRet = i);
  }
}

源碼:Collections.SynchronizedCollection.iterator 迭代器(沒上鎖)

public Iterator<E> iterator() {
  // 這里會直接實現(xiàn)類的迭代器(比如ArrayList,它里面的迭代器肯定是沒上鎖的)
  return c.iterator(); // Must be manually synched by user!
}

二、為什么要有同步容器

因為普通的容器類(比如ArrayList)是線程不安全的,如果是在并發(fā)中使用,我們就需要手動對其加鎖才會安全,這樣的話就很麻煩;

所以就有了同步容器,它來幫我們自動加鎖

下面我們用代碼來對比下

線程不安全的類:ArrayList

public class SyncCollectionDemo {
    
    private List<Integer> listNoSync;

    public SyncCollectionDemo() {
        this.listNoSync = new ArrayList<>();
    }

    public void addNoSync(int temp){
        listNoSync.add(temp);
    }

    public static void main(String[] args) throws InterruptedException {
        SyncCollectionDemo demo = new SyncCollectionDemo();
				// 創(chuàng)建10個線程
        for (int i = 0; i < 10; i++) {
					// 每個線程執(zhí)行100次添加操作
          new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    demo.addNoSync(j);
                }
            }).start();
        }
    }
}

上面的代碼看似沒問題,感覺就算有問題也應(yīng)該是插入的順序比較亂(多線程交替插入)

但實際上運(yùn)行會發(fā)現(xiàn),可能會報錯數(shù)組越界,如下所示:

數(shù)組越界-線程不安全類

原因有二:

因為ArrayList.add操作沒有加鎖,導(dǎo)致多個線程可以同時執(zhí)行add操作add操作時,如果發(fā)現(xiàn)list的容量不足,會進(jìn)行擴(kuò)容,但是由于多個線程同時擴(kuò)容,就會出現(xiàn)擴(kuò)容不足的問題

源碼:ArrayList.grow擴(kuò)容

// 擴(kuò)容方法
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
				// 這里可以看到,每次擴(kuò)容增加一半的容量
  			int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

可以看到,擴(kuò)容是基于之前的容量進(jìn)行的,因此如果多個線程同時擴(kuò)容,那擴(kuò)容基數(shù)就不準(zhǔn)確了,結(jié)果就會有問題

線程安全的類:Collections.SynchronizedList

/**
 * <p>
 *  同步容器類:為什么要有它
 * </p>
 *
 * @author: JavaLover
 * @time: 2021/5/3
 */
public class SyncCollectionDemo {

    private List<Integer> listSync;

    public SyncCollectionDemo() {
      	// 這里包裝一個空的ArrayList
        this.listSync = Collections.synchronizedList(new ArrayList<>());
    }

    public void addSync(int j){
      	// 內(nèi)部是同步操作: synchronized (mutex) {return c.add(e);}
        listSync.add(j);
    }

    public static void main(String[] args) throws InterruptedException {
        SyncCollectionDemo demo = new SyncCollectionDemo();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    demo.addSync(j);
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(1);
      	// 輸出1000
        System.out.println(demo.listSync.size());
    }
}

輸出正確,因為現(xiàn)在ArrayList被Collections包裝成了一個線程安全的類

這就是為啥會有同步容器的原因:因為同步容器使得并發(fā)編程時,線程更加安全

三、同步容器的優(yōu)缺點

一般來說,都是先說優(yōu)點,再說缺點

但是我們這次先說優(yōu)點

優(yōu)點:

  • 并發(fā)編程中,獨立操作是線程安全的,比如單獨的add操作

缺點(是的,優(yōu)點已經(jīng)說完了):

  • 性能差,基本上所有方法都上鎖,完美的詮釋了“寧可錯殺一千,不可放過一個”
  • 復(fù)合操作,還是不安全,比如putIfAbsent操作(如果沒有則添加)
  • 快速失敗機(jī)制,這種機(jī)制會報錯提示ConcurrentModificationException,一般出現(xiàn)在當(dāng)某個線程在遍歷容器時,其他線程恰好修改了這個容器的長度

為啥第三點是缺點呢?

因為它只能作為一個建議,告訴我們有并發(fā)修改異常,但是不能保證每個并發(fā)修改都會爆出這個異常

爆出這個異常的前提如下:

源碼:Vector.Itr.checkForComodification 檢查容器修改次數(shù)

final void checkForComodification() {
  // modCount:容器的長度變化次數(shù), expectedModCount:期望的容器的長度變化次數(shù)
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

那什么情況下并發(fā)修改不會爆出異常呢?有兩種:

1.遍歷沒加鎖的情況:對于第二種同步容器(Collections內(nèi)部類)來說,假設(shè)線程A修改了modCount的值,但是沒有同步到線程B,那么線程B遍歷就不會發(fā)生異常(但實際上問題已經(jīng)存在了,只是暫時沒有出現(xiàn))

2.依賴線程執(zhí)行順序的情況:對于所有的同步容器來說,假設(shè)線程B已經(jīng)遍歷完了容器,此時線程A才開始遍歷修改,那么也不會發(fā)生異常

代碼就不貼了,大家感興趣的可以直接寫幾個線程遍歷試試,多運(yùn)行幾次,應(yīng)該就可以看到效果(不過第一種情況也是基于理論分析,實際代碼我這邊也沒跑出來)

根據(jù)阿里巴巴的開發(fā)規(guī)范:不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請使用 Iterator方式,如果并發(fā)操作,需要對 Iterator 對象加鎖。

這里解釋下,關(guān)于List.remove和Iterator.remove的區(qū)別

  • Iterator.remove:會同步修改expectedModCount=modCount
  • list.remove:只會修改modCount,因為expectedModCount屬于iterator對象的屬性,不屬于list的屬性(但是也可以間接訪問)

源碼:ArrayList.remove移除元素操作

public E remove(int index) {
        rangeCheck(index);
				// 1. 這里修改了 modCount
        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

源碼:ArrayList.Itr.remove迭代器移除元素操作

public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
              	// 1. 這里調(diào)用上面介紹的list.romove,修改modCount
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
              	// 2. 這里再同步更新 expectedModCount
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

由于同步容器的這些缺點,于是就有了并發(fā)容器(下期來介紹)

四、同步容器的使用場景

多用在并發(fā)編程,但是并發(fā)量又不是很大的場景,比如一些簡單的個人博客系統(tǒng)(具體多少并發(fā)量算大,這個也是分很多情況而論的,并不是說每秒處理超過多少個請求,就說是高并發(fā),還要結(jié)合吞吐量、系統(tǒng)響應(yīng)時間等多個因素一起考慮)

具體點來說的話,有以下幾個場景:

  • 寫多讀少,這個時候同步容器和并發(fā)容器的性能差別不大(并發(fā)容器可以并發(fā)讀)
  • 自定義的復(fù)合操作,比如getLast等操作(putIfAbsent就算了,因為并發(fā)容器有默認(rèn)提供這個復(fù)合操作)
  • 等等

總結(jié)

什么是同步容器:就是把容器類同步化,這樣我們在并發(fā)中使用容器時,就不用手動同步,因為內(nèi)部已經(jīng)自動同步了

為什么要有同步容器:因為普通的容器類(比如ArrayList)是線程不安全的,如果是在并發(fā)中使用,我們就需要手動對其加鎖才會安全,這樣的話就很太麻煩;所以就有了同步容器,它來幫我們自動加鎖

同步容器的優(yōu)缺點:

優(yōu)點 獨立操作,線程安全

缺點 復(fù)合操作,還是不安全,性能差快速失敗機(jī)制,只適合bug調(diào)試

同步容器的使用場景

多用在并發(fā)量不是很大的場景,比如個人博客、后臺系統(tǒng)等

具體點來說,有以下幾個場景:

  • 寫多讀少:這個時候同步容器和并發(fā)容器差別不是很大
  • 自定義復(fù)合操作:比如getLast等復(fù)合操作,因為同步容器都是單個操作進(jìn)行上鎖的,所以可以很方便地去拼接復(fù)合操作(記得外部加鎖)

到此這篇關(guān)于Java并發(fā)編程之同步容器的文章就介紹到這了,更多相關(guān)Java同步容器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot3.x版本與Mybatis-Plus不兼容問題

    SpringBoot3.x版本與Mybatis-Plus不兼容問題

    當(dāng)使用3.x版本的SpringBoot結(jié)合Mybatis-Plus時版本不兼容就會報錯,本文就來介紹一下這個問題的解決方法,感興趣的可以了解一下
    2024-03-03
  • Java遞歸算法詳解(動力節(jié)點整理)

    Java遞歸算法詳解(動力節(jié)點整理)

    Java遞歸算法是基于Java語言實現(xiàn)的遞歸算法。遞歸算法對解決一大類問題很有效,它可以使算法簡潔和易于理解。接下來通過本文給大家介紹Java遞歸算法相關(guān)知識,感興趣的朋友一起學(xué)習(xí)吧
    2017-03-03
  • Spring Boot數(shù)據(jù)庫鏈接池配置方法

    Spring Boot數(shù)據(jù)庫鏈接池配置方法

    這篇文章主要介紹了Spring Boot數(shù)據(jù)庫鏈接池配置方法,需要的朋友可以參考下
    2017-04-04
  • 用Java設(shè)計實現(xiàn)多實例多庫查詢方式

    用Java設(shè)計實現(xiàn)多實例多庫查詢方式

    這篇文章主要介紹了用Java設(shè)計實現(xiàn)多實例多庫查詢方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • java多態(tài)注意項小結(jié)

    java多態(tài)注意項小結(jié)

    面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài)。從一定角度來看,封裝和繼承幾乎都是為多態(tài)而準(zhǔn)備的。今天通過本文給大家介紹java多態(tài)注意項總結(jié),感興趣的朋友一起看看吧
    2021-10-10
  • JavaSwing BorderLayout 邊界布局的實現(xiàn)代碼

    JavaSwing BorderLayout 邊界布局的實現(xiàn)代碼

    這篇文章主要介紹了JavaSwing BorderLayout 邊界布局的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • idea中方法、注釋、導(dǎo)入類折疊或是展開的設(shè)置方法

    idea中方法、注釋、導(dǎo)入類折疊或是展開的設(shè)置方法

    這篇文章主要介紹了idea中方法、注釋、導(dǎo)入類折疊或是展開的設(shè)置,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Java二叉樹的四種遍歷方式詳解

    Java二叉樹的四種遍歷方式詳解

    這篇文章主要介紹了Java二叉樹的四種遍歷,二叉樹的遍歷可以分為前序、中序、后序、層次遍歷,需要的朋友可以參考下
    2021-11-11
  • Java讀取properties文件之中文亂碼問題及解決

    Java讀取properties文件之中文亂碼問題及解決

    這篇文章主要介紹了Java讀取properties文件之中文亂碼問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • MyBatis?Xml映射文件之字符串替換方式

    MyBatis?Xml映射文件之字符串替換方式

    這篇文章主要介紹了MyBatis?Xml映射文件之字符串替換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評論