1. 程式人生 > >014.多執行緒-併發包

014.多執行緒-併發包

在日常開發中,我們最常用的集合和字典莫過於ArrayList和HashMap了。

	ArrayList,內部是通過陣列實現的,它允許對元素進行快速隨機訪問。
	但是,當進行增加儲存功能,以及插入、刪除元素時,需要對陣列進行復制以及移動,代價比較高。
	它適合隨機查詢和遍歷,不適合插入和刪除。

	HashMap,由陣列+連結串列組成的,陣列是HashMap的主體,連結串列則是主要為了解決雜湊衝突而存在的。
	如果定位到的陣列位置不含連結串列,那麼對於查詢,新增等操作很快,僅需一次定址即可;
	如果定位到的陣列包含連結串列,對於新增操作,其時間複雜度為O(n),首先遍歷連結串列,存在即覆蓋,否則新增;
	對於查詢操作來講,仍需遍歷連結串列,然後通過key物件的equals方法逐一比對查詢。
	所以,效能考慮,HashMap中的連結串列出現越少,效能才會越好。

但是,ArrayList和HashMap都是執行緒不安全的。
與此同時,相對應的 Vector和HashTable卻是執行緒安全的,但是,效率太低了。

	Vector與ArrayList一樣,也是通過陣列實現的,不同的是它支援執行緒的同步。
	Vector的add和get均被synchronized修飾,
	即:同一時刻,只有一個執行緒可以讀寫Vector。

	HashTable類似於HashMap,只不過HashMap允許null key 和 null value,HashTable不允許。
	不過它的put和get方法均被synchronized修飾,
	即:同一時刻,只有一個執行緒可以讀寫HashTable。


因此為提升效率的併發包應運而生。

Collections.synchronizedList


        List<String> list1 = new CopyOnWriteArrayList<String>();
        List<String> list2 = Collections.synchronizedList(new ArrayList<String>());

CopyOnWriteArrayList與Collections.synchronizedList效能比較

  • 讀操作時,CopyOnWriteArrayList效能相對較好些。
  • 寫操作時,Collections.synchronizedList效能相對較好些。

ConcurrentHashMap

ConcurrentHashMap內部使用段(Segment)來表示不同的部分,
每個部分都相當於一個HashTable,都擁有自己的鎖。
只要多個操作不同時發生在一個段上,就可以併發進行。
其中Segment陣列長度為16,不可以擴容。
ConcurrentHashMap的初始化容量會平均分配給每個Segment。
其中的負載因子是針對每個Segment的,
當實際容量大於閾值(負載因子乘以Segment容量)時,會擴容當前Segment。

    /**
     * The default load factor for this table, used when not
     * otherwise specified in a constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The default concurrency level for this table, used when not
     * otherwise specified in a constructor.
     */
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;

CountDownLatch

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

package cn.qbz.thread;

import java.util.concurrent.CountDownLatch;

public class Test111901 {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println("子執行緒" + Thread.currentThread().getName() + "開始執行...");
                    for (int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("子執行緒" + Thread.currentThread().getName() + "結束執行...");
                    countDownLatch.countDown();
                }
            }).start();
        }

        System.out.println("主執行緒等待子執行緒執行結束....");
        countDownLatch.await();
        System.out.println("主執行緒執行結束......");
    }
}

如果 final CountDownLatch countDownLatch = new CountDownLatch(4);
主執行緒將會一直阻塞在 countDownLatch.await();


CyclicBarrier

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

package cn.qbz.thread;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Test111902 {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
        for (int i = 0; i < 4; i++) {
            new ThreadTest111902(cyclicBarrier).start();
        }
        System.out.println("主執行緒執行結束......");
    }
}

class ThreadTest111902 extends Thread {
    private CyclicBarrier cyclicBarrier;

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

    @Override
    public void run() {
        System.out.println("子執行緒" + getName() + ":開始執行");
        try {
            Thread.sleep(3000);

            System.out.println("子執行緒" + getName() + ":結束執行");
            cyclicBarrier.await();

            System.out.println("子執行緒" + getName() + ":所有執行緒執行結束....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}


Semaphore

Semaphore是一種基於計數的訊號量。
它可以設定一個閾值,多個執行緒競爭獲取許可訊號,
超過閾值後,執行緒申請許可訊號將會被阻塞。
Semaphore可以用來構建一些物件池,資源池之類的,比如資料庫連線池。
我們也可以建立計數為1的Semaphore,將其作為一種類似互斥鎖的機制,這也叫二元訊號量,表示兩種互斥狀態。
它的用法如下:
availablePermits函式用來獲取當前可用的資源數量
wc.acquire(); //申請資源
wc.release();// 釋放資源

	// 建立一個計數閾值為5的訊號量物件  
   	// 只能5個執行緒同時訪問  
   	Semaphore semp = new Semaphore(5);  
   	  
   	try {  
   	    // 申請許可  
   	    semp.acquire();  
   	    try {  
   	        // 業務邏輯  
   	    } catch (Exception e) {  
   	  
   	    } finally {  
   	        // 釋放許可  
   	        semp.release();  
   	    }  
   	} catch (InterruptedException e) {  
   	  
   	}  

code of semaphore demo:

有4個業務辦理視窗,等候區共有30人,模擬。

package cn.qbz.thread;

import java.util.concurrent.Semaphore;

public class Test111903 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(4);
        for (int i = 0; i < 30; i++) {
            new ThreadTest111903(semaphore, "第" + i + "個").start();
        }
        System.out.println("主執行緒執行結束......");
    }
}

class ThreadTest111903 extends Thread {
    private Semaphore semaphore;
    private String name;

    public ThreadTest111903(Semaphore semaphore, String name) {
        this.semaphore = semaphore;
        this.name = name;
    }

    @Override
    public void run() {
        //判斷是否可以申請許可
        int availablePermits = semaphore.availablePermits();
        if (availablePermits > 0) {
            System.out.println(name + "...搶到了視窗");
        } else {
            System.out.println(name + "...沒搶到了視窗");
        }

        try {
            //申請資源,如果沒有可用的資源,則阻塞在這裡
            //有了資源後,再從這裡繼續執行
            semaphore.acquire();
            System.out.println(name + "。。。開始辦理業務");
            Thread.sleep(Math.round(1000));
            System.out.println(name + "。。。結束辦理業務");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //釋放資源
            semaphore.release();
        }
    }
}