常用的併發包學習
併發包
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
-
ArrayList是最常用的List實現類,內部是通過數組實現的,它允許對元素進行快速隨機訪問。當從ArrayList的中間位置插入或者刪除元素時,需要對數組進行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。ArrayList的缺點是每個元素之間不能有間隔。
-
Vector與ArrayList一樣,也是通過數組實現的,不同的是它支持線程的同步,即某一時刻只有一個線程能夠寫Vector,避免多線程同時寫而引起的不一致性,但實現同步需要很高的花費,訪問它比訪問ArrayList慢很多
-
ArrayList添加方法源碼
-
Vector添加源碼(加鎖了synchronized關鍵字)
2 HasTable
-
HashMap是非線程安全的,而HasTable的內部方法都被synchronized修飾了,所以是線程安全的。
-
因爲線程安全的問題,HashMap要比HashTable效率高一點,HashTable除了在多線程開發中基本被淘汰。
-
HashMap允許有null值的存在(key只能一個爲null,value都可爲空),而在HashTable中是都不能爲空的
-
HashMap添加方法的源碼
-
HashTable添加方法的源碼
3 Collections.synchronized *
此接口方法是幹什麼的呢,他完完全全的可以把List、Map、Set接口底下的集合變成線程安全的集合
Collections.synchronized * :原理是什麼,我猜的話是代理模式:Java代理模式理解
4 ConcurrentHashMap
-
我們不是有HashTable來實現線程安全Key/Value儲存值了嗎,爲什麼還要他
-
ConcurrentHashMap是Java5中支持高併發、高吞吐量的線程安全HashMap實現。它由Segment數組結構和HashEntry數組結構組成。Segment數組在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵-值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構;一個Segment裏包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素;每個Segment守護着一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。
-
總結:
1、底層採用分段的數組+鏈表實現,線程安全
2、通過把整個Map分爲N個Segment,可以提供相同的線程安全,但是效率提升N倍,默認提升16倍。
3、並且讀操作不加鎖,由於HashEntry的value變量是 volatile的,也能保證讀取到最新的值。
4、Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術
5、擴容:段內擴容(段內元素超過該段對應Entry數組長度的75%觸發擴容,不會對整個Map進行擴容),插入前檢測需不需要擴容,有效避免無效擴容