Java併發編程-3

併發包

Java多線程相關類的實現都在Java的併發包concurrent,concurrent包主要包含3部分內容,第一個是atomic包,裏面主要是一些原子類,比如AtomicInteger、AtomicIntegerArray等;第二個是locks包,裏面主要是鎖相關的類,比如ReentrantLock、Condition等;第三個就是屬於concurrent包的內容,主要包括線程池相關類(Executors)、阻塞集合類(BlockingQueue)、併發Map類(ConcurrentHashMap)、線程相關類(Thread、Runnable、Callable)等。

同步容器

Vector與ArrayList區別

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

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

注意: Vector線程安全、ArrayList線程不安全,通過源碼發現,vector的方法加了同步synchrozined,arraylist的方法並沒有加同步,是不安全的。

Vector源碼

Add方法源碼類

Arraylist源碼

Add方法源碼

HasTable與HasMap

1.HashMap不是線程安全的 

HastMap是一個接口 是map接口的子接口,是將鍵映射到值的對象,其中鍵和值都是對象,並且不能包含重複鍵,但可以包含重複值。HashMap允許null key和null value,而hashtable不允許。

2.HashTable是線程安全的一個Collection。

3.HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口,主要區別在於HashMap允許空(null)鍵值(key),由於非線程安全,效率上可能高於Hashtable。
HashMap允許將null作爲一個entry的key或者value,而Hashtable不允許。
HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。

注意: HashTable線程安全,HashMap線程不安全

源碼分析

synchronizedMap

 Collections.synchronized*(m) 將線程不安全額集合變爲線程安全集合

ConcurrentHashMap

ConcurrentMap接口下有倆個重要的實現 :
ConcurrentHashMap
ConcurrentskipListMap (支持併發排序功能。彌補ConcurrentHas hMa p)
ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個
小的HashTable,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以並
發進行。把一個整體分成了16個段(Segment.也就是最高支持16個線程的併發修改操作。
這也是在重線程場景時減小鎖的粒度從而降低鎖競爭的一種方案。並且代碼中大多共享變
量使用volatile關鍵字聲明,目的是第一時間獲取修改的內容,性能非常好。

 

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

public class Test002 {

 

public static void main(String[] args) throws InterruptedException {

System.out.println("等待子線程執行完畢...");

CountDownLatch countDownLatch = new CountDownLatch(2);

new Thread(new Runnable() {

 

@Override

public void run() {

System.out.println("子線程," + Thread.currentThread().getName() + "開始執行...");

countDownLatch.countDown();// 每次減去1

System.out.println("子線程," + Thread.currentThread().getName() + "結束執行...");

}

}).start();

new Thread(new Runnable() {

 

@Override

public void run() {

System.out.println("子線程," + Thread.currentThread().getName() + "開始執行...");

countDownLatch.countDown();

System.out.println("子線程," + Thread.currentThread().getName() + "結束執行...");

}

}).start();

 

countDownLatch.await();// 調用當前方法主線程阻塞  countDown結果爲0, 阻塞變爲運行狀態

System.out.println("兩個子線程執行完畢....");

System.out.println("繼續主線程執行..");

}

 

}

 

CyclicBarrier

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

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

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

 

class Writer extends Thread {

private CyclicBarrier cyclicBarrier;

public Writer(CyclicBarrier cyclicBarrier){

 this.cyclicBarrier=cyclicBarrier;

}

@Override

public void run() {

System.out.println("線程" + Thread.currentThread().getName() + ",正在寫入數據");

try {

Thread.sleep(3000);

} catch (Exception e) {

// TODO: handle exception

}

System.out.println("線程" + Thread.currentThread().getName() + ",寫入數據成功.....");

 

try {

cyclicBarrier.await();

} catch (Exception e) {

}

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

}

 

}

 

public class Test001 {

 

public static void main(String[] args) {

CyclicBarrier cyclicBarrier=new CyclicBarrier(5);

for (int i = 0; i < 5; i++) {

Writer writer = new Writer(cyclicBarrier);

writer.start();

}

}

 

}

 

 

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) {  

       

     }  

案例:

需求: 一個廁所只有3個坑位,但是有10個人來上廁所,那怎麼辦?假設10的人的編號分別爲1-10,並且1號先到廁所,10號最後到廁所。那麼1-3號來的時候必然有可用坑位,順利如廁,4號來的時候需要看看前面3人是否有人出來了,如果有人出來,進去,否則等待。同樣的道理,4-10號也需要等待正在上廁所的人出來後才能進去,並且誰先進去這得看等待的人是否有素質,是否能遵守先來先上的規則。

代碼:

 

class Parent implements Runnable {

private String name;

private Semaphore wc;

public Parent(String name,Semaphore wc){

this.name=name;

this.wc=wc;

}

@Override

public void run() {

try {

// 剩下的資源(剩下的茅坑)

int availablePermits = wc.availablePermits();

if (availablePermits > 0) {

System.out.println(name+"天助我也,終於有茅坑了...");

} else {

System.out.println(name+"怎麼沒有茅坑了...");

}

//申請茅坑 如果資源達到3次,就等待

wc.acquire();

System.out.println(name+"終於輪我上廁所了..爽啊");

   Thread.sleep(new Random().nextInt(1000)); // 模擬上廁所時間。

System.out.println(name+"廁所上完了...");

wc.release();

 

} catch (Exception e) {

 

}

}

}

public class TestSemaphore02 {

public static void main(String[] args) {

// 一個廁所只有3個坑位,但是有10個人來上廁所,那怎麼辦?假設10的人的編號分別爲1-10,並且1號先到廁所,10號最後到廁所。那麼1-3號來的時候必然有可用坑位,順利如廁,4號來的時候需要看看前面3人是否有人出來了,如果有人出來,進去,否則等待。同樣的道理,4-10號也需要等待正在上廁所的人出來後才能進去,並且誰先進去這得看等待的人是否有素質,是否能遵守先來先上的規則。

         Semaphore semaphore = new Semaphore(3);

for (int i = 1; i <=10; i++) {

 Parent parent = new Parent("第"+i+"個人,",semaphore);

 new Thread(parent).start();

}

}

}

 

 

 

併發隊列

在併發隊列上JDK提供了兩套實現,一個是以ConcurrentLinkedQueue爲代表的高性能隊

列,一個是以BlockingQueue接口爲代表的阻塞隊列,無論哪種都繼承自Queue。

ConcurrentLinkedDeque
ConcurrentLinkedQueue : 是一個適用於高併發場景下的隊列,通過無鎖的方式,實現
了高併發狀態下的高性能,通常ConcurrentLinkedQueue性能好於BlockingQueue.它
是一個基於鏈接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。頭是最先
加入的,尾是最近加入的,該隊列不允許null元素。
ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,後者不會。

 

ConcurrentLinkedDeque q = new ConcurrentLinkedDeque();

q.offer("xx");

q.offer("碼雲");

q.offer("xxx");

q.offer("xxxx");

q.offer("艾姐");

//從頭獲取元素,刪除該元素

System.out.println(q.poll());

//從頭獲取元素,不刪除該元素

System.out.println(q.peek());

//獲取總長度

System.out.println(q.size());

 

 

BlockingQueue

阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:

在隊列爲空時,獲取元素的線程會等待隊列變爲非空。
當隊列滿時,存儲元素的線程會等待隊列可用。 

阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。

在Java中,BlockingQueue的接口位於java.util.concurrent 包中(在Java5版本開始提供),由上面介紹的阻塞隊列的特性可知,阻塞隊列是線程安全的。

在新增的Concurrent包中,BlockingQueue很好的解決了多線程中,如何高效安全“傳輸”數據的問題。通過這些高效並且線程安全的隊列類,爲我們快速搭建高質量的多線程程序帶來極大的便利

 

ArrayBlockingQueue

ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。

ArrayBlockingQueue是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。下面

是一個初始化和使用ArrayBlockingQueue的例子:

ArrayBlockingQueue<String> arrays = new ArrayBlockingQueue<String>(3);

arrays.add("李四");

 arrays.add("張軍");

arrays.add("張軍");

// 添加阻塞隊列

arrays.offer("張三", 1, TimeUnit.SECONDS);

 

 

LinkedBlockingQueue

LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是採用了默認大小爲Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。

和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。下面是一個初始化和使LinkedBlockingQueue的例子:

LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);

linkedBlockingQueue.add("張三");

linkedBlockingQueue.add("李四");

linkedBlockingQueue.add("李四");

System.out.println(linkedBlockingQueue.size());

PriorityBlockingQueue

PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣。需要注

意,PriorityBlockingQueue中允許插入null對象。

所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就

是按照我們對這個接口的實現來定義的。

另外,我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器並不保證按照優先級順

序進行迭代。

下面我們舉個例子來說明一下,首先我們定義一個對象類型,這個對象需要實現Comparable接口:

SynchronousQueue

SynchronousQueue隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。

使用BlockingQueue模擬生產者與消費

class ProducerThread implements Runnable {

private BlockingQueue queue;

private volatile boolean flag = true;

private static AtomicInteger count = new AtomicInteger();

public ProducerThread(BlockingQueue queue) {

this.queue = queue;

}

 

@Override

public void run() {

try {

System.out.println("生產線程啓動...");

while (flag) {

System.out.println("正在生產數據....");

String data = count.incrementAndGet()+"";

// 將數據存入隊列中

boolean offer = queue.offer(data, 2, TimeUnit.SECONDS);

if (offer) {

System.out.println("生產者,存入" + data + "到隊列中,成功.");

} else {

System.out.println("生產者,存入" + data + "到隊列中,失敗.");

}

Thread.sleep(1000);

}

} catch (Exception e) {

 

} finally {

System.out.println("生產者退出線程");

}

 

}

 

public void stop() {

this.flag = false;

}

}

 

class ConsumerThread implements Runnable {

private BlockingQueue<String> queue;

private volatile boolean flag = true;

 

public ConsumerThread(BlockingQueue<String> queue) {

this.queue = queue;

 

}

 

@Override

public void run() {

System.out.println("消費線程啓動...");

try {

while (flag) {

System.out.println("消費者,正在從隊列中獲取數據..");

String data = queue.poll(2, TimeUnit.SECONDS);

if (data != null) {

System.out.println("消費者,拿到隊列中的數據data:" + data);

Thread.sleep(1000);

} else {

System.out.println("消費者,超過2秒未獲取到數據..");

flag = false;

}

 

 

}

} catch (Exception e) {

               e.printStackTrace();

} finally {

System.out.println("消費者退出線程...");

}

 

}

 

}

 

public class ProducerAndConsumer {

public static void main(String[] args) throws InterruptedException {

BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);

ProducerThread producerThread1 = new ProducerThread(queue);

ProducerThread producerThread2 = new ProducerThread(queue);

ConsumerThread consumerThread1 = new ConsumerThread(queue);

        Thread t1 = new Thread(producerThread1);

        Thread t2 = new Thread(producerThread2);

        Thread c1 = new Thread(consumerThread1);

        t1.start();

        t2.start();

        c1.start();

 

        // 執行10s

        Thread.sleep(10 * 1000);

        producerThread1.stop();

        producerThread2.stop();

   

}

}

 

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