【Java併發編程學習 5】常用的併發包與併發容器學習

併發包

1 CountDownLatch (計數器)

1.1 什麼是CountDownLatch

CountDownLatch 類位於java.util.concurrent包下,利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他3個任務執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。

1.2 CountDownLatch 的原理

CountDownLatch是通過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。

1.3 CountDownLatch代碼示例

package com.lijie;

        import java.util.concurrent.CountDownLatch;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //定義CountDownLatch記數器
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ",子線程開始執行");
                countDownLatch.countDown();//計數器值每次減去1
                System.out.println(Thread.currentThread().getName() + ",子線程結束執行");
            }
        }, "線程1").start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ",子線程開始執行");
                countDownLatch.countDown();//計數器值每次減去1
                System.out.println(Thread.currentThread().getName() + ",子線程結束執行");
            }
        }, "線程2").start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ",子線程開始執行");
                countDownLatch.countDown();//計數器值每次減去1
                System.out.println(Thread.currentThread().getName() + ",子線程結束執行");
            }
        }, "線程3").start();

        countDownLatch.await();// 減去爲0時,恢復任務繼續執行
        System.out.println("三個子線程執行完畢");
        System.out.println("主線程繼續執行");
        for (int i = 0; i < 10; i++) {
            System.out.println("main,i:" + i);
        }
    }
}

2 CyclicBarrier (迴環柵欄)

CyclicBarrier它的作用就是會讓所有線程都等待完成後纔會繼續下一步行動。

CyclicBarrier初始化時規定一個數目,然後計算調用了CyclicBarrier.await()進入等待的線程數。當線程數達到了這個數目時,所有進入等待狀態的線程被喚醒並繼續。

CyclicBarrier就象它名字的意思一樣,可看成是個障礙, 所有的線程必須到齊後才能一起通過這個障礙。

CyclicBarrier初始時還可帶一個Runnable的參數, 此Runnable任務在CyclicBarrier的數目達到後,所有其它線程被喚醒前被執行。

2.1 CyclicBarrier代碼示例

package com.lijie;

import java.util.concurrent.CyclicBarrier;

class Writer extends Thread {
	//定義屏障器
    private CyclicBarrier cyclicBarrier;

    public Writer(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    public void run() {
        System.out.println("線程" + Thread.currentThread().getName() + ",正在寫入數據");
        try {
            //此方法表示線程需要再次同步
            cyclicBarrier.await();
        } catch (Exception e) {

        }
        System.out.println("線程" + Thread.currentThread().getName() + ",寫入數據成功");
        try {
            //此方法表示線程需要再次同步
            cyclicBarrier.await();
        } catch (Exception e) {

        }
        System.out.println("所有線程執行完畢..........");
    }
}

public class Test1 {
    public static void main(String[] args) {
        //定義爲5,表示這五個線程需要同步
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            Writer writer = new Writer(cyclicBarrier);
            writer.start();
        }
    }
}

3 Semaphore (信號量)

Semaphore 是 synchronized 的加強版,作用是控制線程的併發數量。就這一點而言,單純的synchronized 關鍵字是實現不了的。

Semaphore是一種基於計數的信號量。它可以設定一個閾值,基於此,多個線程競爭獲取許可信號,做自己的申請後歸還,超過閾值後,線程申請許可信號將會被阻塞。Semaphore可以用來構建一些對象池,資源池之類的,比如數據庫連接池,我們也可以創建計數爲1的Semaphore,將其作爲一種類似互斥鎖的機制,這也叫二元信號量,表示兩種互斥狀態。它的用法如下:

availablePermits函數用來獲取當前可用的資源數量
wc.acquire(); //申請資源
wc.release();// 釋放資源

3.1 Semaphore代碼示例

10 個人訪問, 3 個併發數訪問

package com.lijie;

import java.util.Random;
import java.util.concurrent.Semaphore;

class ThreadDemo extends Thread {
	private String name;
	//定義線程併發數量屬性(計數信號量)
	private Semaphore hotel;

	public ThreadDemo(String name, Semaphore hotel) {
		this.name = name;
		this.hotel = hotel;
	}

	public void run() {
		// 剩下的資源
		int availablePermits = hotel.availablePermits();
		if (availablePermits > 0) {
			System.out.println(name + "飯店有位置了,我要開動了");
		} else {
			System.out.println(name + "怎麼沒有位置了,餓死了,等會吧");
		}
		try {
			// 申請資源
			hotel.acquire();
		} catch (InterruptedException e) {

		}
		System.out.println(name + "終於上吃上飯了,舒服" + ",剩下飯店位置:" + hotel.availablePermits());
		try {
			Thread.sleep(new Random().nextInt(1000));
		} catch (Exception e) {

		}
		System.out.println(name + "我吃完啦!,走人");
		// 釋放資源
		hotel.release();
	}
}

public class TestSemaphore {

	public static void main(String[] args) {
		//定義線程併發數量
		Semaphore semaphore = new Semaphore(3);
		for (int i = 1; i <= 10; i++) {
			ThreadDemo threadDemo = new ThreadDemo("第" + i + "個人說:", semaphore);
			threadDemo.start();
		}
	}
}

併發容器

容器用的最多的就是ArrayList,LinkedList ,HashMap等等

在多線程開發中就不能亂用容器,如果使用了未加鎖(非同步)的的集合,你的數據就會非常的混亂。由此在多線程開發中需要使用的容器必須是加鎖(同步)的容器。

常用的同步容器介紹

1 Vector

  1. ArrayList是最常用的List實現類,內部是通過數組實現的,它允許對元素進行快速隨機訪問。當從ArrayList的中間位置插入或者刪除元素時,需要對數組進行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。ArrayList的缺點是每個元素之間不能有間隔。

  2. Vector與ArrayList一樣,也是通過數組實現的,不同的是它支持線程的同步,即某一時刻只有一個線程能夠寫Vector,避免多線程同時寫而引起的不一致性,但實現同步需要很高的花費,訪問它比訪問ArrayList慢很多

  3. ArrayList添加方法源碼
    在這裏插入圖片描述

  4. Vector添加源碼(加鎖了synchronized關鍵字)
    在這裏插入圖片描述

2 HasTable

  1. HashMap是非線程安全的,而HasTable的內部方法都被synchronized修飾了,所以是線程安全的。

  2. 因爲線程安全的問題,HashMap要比HashTable效率高一點,HashTable除了在多線程開發中基本被淘汰。

  3. HashMap允許有null值的存在(key只能一個爲null,value都可爲空),而在HashTable中是都不能爲空的

  4. HashMap添加方法的源碼
    在這裏插入圖片描述

  5. HashTable添加方法的源碼
    在這裏插入圖片描述

3 Collections.synchronized *

此接口方法是幹什麼的呢,他完完全全的可以把List、Map、Set接口底下的集合變成線程安全的集合

Collections.synchronized * :原理是什麼,我猜的話是代理模式:Java代理模式理解

在這裏插入圖片描述

4 ConcurrentHashMap

  1. 我們不是有HashTable來實現線程安全Key/Value儲存值了嗎,爲什麼還要他

  2. ConcurrentHashMap是Java5中支持高併發、高吞吐量的線程安全HashMap實現。它由Segment數組結構和HashEntry數組結構組成。Segment數組在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵-值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構;一個Segment裏包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素;每個Segment守護着一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。

  3. 總結:
    1、底層採用分段的數組+鏈表實現,線程安全
    2、通過把整個Map分爲N個Segment,可以提供相同的線程安全,但是效率提升N倍,默認提升16倍。
    3、並且讀操作不加鎖,由於HashEntry的value變量是 volatile的,也能保證讀取到最新的值。
    4、Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術
    5、擴容:段內擴容(段內元素超過該段對應Entry數組長度的75%觸發擴容,不會對整個Map進行擴容),插入前檢測需不需要擴容,有效避免無效擴容
    在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章