JDK1.5引入的concurrent包

  併發是伴隨着多核處理器的誕生而產生的,爲了充分利用硬件資源,誕生了多線程技術。可是多線程又存在資源競爭的問題,引起了同步和互斥,並帶來線程安全的問題。因而,從jdk1.5開始,引入了concurrent包來解決這些問題。html

  java.util.concurrent 包是專爲 Java併發編程而設計的包。
java

在Java中,當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些線程將如何交替進行,在主調代碼中不須要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼稱這個類是線程安全的。

    通常來講,concurrent包基本上由有3個package組成 : 算法

   java.util.concurrent:提供大部分關於併發的接口和類,如BlockingQueue,Callable,ConcurrentHashMap,ExecutorService, Semaphore等 ;
  java.util.concurrent.atomic:提供全部原子操做的類, 如AtomicInteger, AtomicLong等;
  java.util.concurrent.locks:提供鎖相關的類, 如Lock, ReentrantLock, ReadWriteLock, Condition等。 

   concurrent包下的全部類能夠分爲以下幾大類:編程

    locks部分:顯式鎖(互斥鎖和速寫鎖)相關,如ReentrantLock,ReentrantReadWriteLock等;
    atomic部分:原子變量類相關,是構建非阻塞算法的基礎,如AtomicInteger,AtomicBoolean,AtomicLong,AtomicReference等;
    executor部分:線程池相關,如ExecutorService,Callable,Future等;
    collections部分:併發容器相關,如BlockingQueue,Deque,ConcurrentMap等;
    tools部分:同步工具相關,如CountDownLatch,CyclicBarrier,Semaphore,Executors,Exchanger等。

  JUC的類圖結構以下所示:安全

  concurrent包的優勢有:
  ①功能豐富,諸如線程池(ThreadPoolExecutor),CountDownLatch等併發編程中須要的類已經有現成的實現,不須要本身去實現一套; 相比較而言,jdk1.4對多線程編程的主要支持幾乎只有Thread, Runnable,synchronized等。synchronized和JDK5以後的Lock均是悲觀鎖(悲觀鎖通常是一我的在使用的時候,另外一我的不能用,因此性能極低,所能支持的併發量就不高)。
  ②concurrent包裏面的一些操做是基於硬件級別的CAS(compare and swap,比較再賦值),就是在cpu級別提供了原子操做,簡單的說就是能夠提供無阻塞、無鎖定的算法; 而現代cpu大部分都是支持這種算法的。JUC(java.util.concurrent)是基於樂觀鎖的,既能保證數據不混亂,又能保證性能。多線程

  version-(版本管理)就是基於樂觀鎖機制-->拿着咱們指望的結果,和現有結果進行比對,若是是相同的,就賦值,若是不是相同的,就重試。
  CAS:有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。  
  CAS算法內部是經過JNI--native方法來實現: 由java底層的C語言或者C++實現。 

  通常狀況下:同步容器是有使用價值的。有時候,咱們的異步容器,好比ArrayList,在併發環境下,會有這些問題:①數據紊亂;②java.util.ConcurrentModificationException。這都是對於集合的讀寫狀態不一致形成的問題。
   咱們能夠這樣構建同步容器:    併發

     Collections.synchronizedList(new ArrayList<>());
     Collections.synchronizedMap()      
     Collections.synchronizedSet()

   上面的方法能夠實現同步容器,可是使用了悲觀鎖,於是效率不高。
  使用JUC體系中提供的容器,如:ConcurrentHashMap,則有這樣的優點:①不會出現同步問題,數據是正常的;②速度相對較快  ,就算沒有hashmap快,也比hashtable快的多。其實,ConcurrentHashMap內部也是有同步的,在這方面面和hashtable沒有區別。那麼,ConcurrentHashMap快在哪裏?主要是由於,其內部劃分了不少segment區域,當不一樣的線程操做不一樣的segment的時候,其實仍是一個異步操做;只有當不一樣線程操做同一個segment的時候,纔會發生同步操做,因此速度很快。一個ConcurrentHashMap內部最多能有16個segment。異步

  咱們接下來看一個很是有用的類CountDownLatch, 它是一個能夠用來在一個進程中等待多個線程完成任務的類。在此給出一個應用場景:某個主線程接到一個任務,起了n個子線程去完成,可是主線程須要等待這n個子線程都完成任務之後纔開始執行某個操做。詳見代碼:ide

package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
/**
 * 主線程會在啓動的子線程徹底結束後再繼續執行
 */
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.demoCountDown();
    }

    public void demoCountDown() {
        int count = 10;
        final CountDownLatch l = new CountDownLatch(count);
        for (int i = 0; i < count; ++i) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.currentThread().sleep(2 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread -" + index + "- has finished...");
                    l.countDown();
                }
            }).start();
        }
        try {
            l.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("now all threads have finished");
    }
}

   執行結果以下所示:函數

thread -9- has finished...
thread -8- has finished...
thread -0- has finished...
thread -4- has finished...
thread -1- has finished...
thread -2- has finished...
thread -5- has finished...
thread -6- has finished...
thread -3- has finished...
thread -7- has finished...
now all threads have finished

   接下來,咱們再看下Atomic相關的類, 好比AtomicLong, AtomicInteger等。簡單來講,這些類都是線程安全的,支持無阻塞無鎖定的。

package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
/**
 * 測試AtomicLong與long
 */
public class AtomicTest {
    public static void main(String[] args) {
        AtomicTest test = new AtomicTest();
        test.testAtomic();
    }

    public void testAtomic() {
        final int loopcount = 10000;
        int threadcount = 10;
        final NonSafeSeq seq1 = new NonSafeSeq();
        final SafeSeq seq2 = new SafeSeq();
        final CountDownLatch l = new CountDownLatch(threadcount);
        for (int i = 0; i < threadcount; ++i) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < loopcount; ++j) {
                        seq1.inc();
                        seq2.inc();
                    }
                    System.out.println("finished : " + index);
                    l.countDown();
                }
            }).start();
        }
        try {
            l.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("both have finished....");
        System.out.println("NonSafeSeq:" + seq1.get());
        System.out.println("SafeSeq with atomic: " + seq2.get());
    }
    class NonSafeSeq {
        private long count = 0;
        public void inc() {
            count++;
        }
        public long get() {
            return count;
        }
    }

    class SafeSeq {
        private AtomicLong count = new AtomicLong(0);
        public void inc() {
            count.incrementAndGet();
        }
        public long get() {
            return count.longValue();
        }
    }
}

  上述代碼執行以下:

finished : 0
finished : 3
finished : 2
finished : 6
finished : 9
finished : 5
finished : 8
finished : 1
finished : 4
finished : 7
both have finished....
NonSafeSeq:98454
SafeSeq with atomic: 100000

   其中,NonSafeSeq是做爲對比的類,直接放一個private long count不是線程安全的,而SafeSeq裏面放了一個AtomicLong,是線程安全的;能夠直接調用incrementAndGet來增長。經過上述執行結果能夠看到,10個線程,每一個線程運行了10,000次,理論上應該有100,000次增長,使用了普通的long是非線程安全的,而使用了AtomicLong是線程安全的。須要注意的是,這個例子也說明,雖然long自己的單個設置是原子的,要麼成功要麼不成功,可是諸如count++這樣的操做就不是線程安全的,由於這包括了讀取和寫入兩步操做。

   在jdk 1.4時代,線程間的同步主要依賴於synchronized關鍵字,本質上該關鍵字是一個對象鎖,能夠加在不一樣的instance上或者class上。
  concurrent包提供了一個能夠替代synchronized關鍵字的ReentrantLock,簡單的說,你能夠new一個ReentrantLock, 而後經過lock.lock和lock.unlock來獲取鎖和釋放鎖;須要注意的是,必須將unlock放在finally塊裏面。reentrantlock的好處有 :
  ①更好的性能;
  ②提供同一個lock對象上不一樣condition的信號通知;
  ③還提供lockInterruptibly這樣支持響應中斷的加鎖過程,意思是說你試圖去加鎖,可是當前鎖被其餘線程hold住,而後你這個線程能夠被中斷。  

package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 測試ReentrantLock
 */
public class ReentrantLockTest {
    public static void main(String[] args) {
        ReentrantLockTest lockTest = new ReentrantLockTest();
        lockTest.demoLock();
    }

    public void demoLock() {
        final int loopcount = 10000;
        int threadcount = 10;
        final SafeSeqWithLock seq = new SafeSeqWithLock();
        final CountDownLatch l = new CountDownLatch(threadcount);
        for (int i = 0; i < threadcount; ++i) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < loopcount; ++j) {
                        seq.inc();
                    }
                    System.out.println("finished : " + index);
                    l.countDown();
                }
            }).start();
        }
        try {
            l.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("both have finished....");
        System.out.println("SafeSeqWithLock:" + seq.get());
    }

    class SafeSeqWithLock {
        private long count = 0;
        private ReentrantLock lock = new ReentrantLock();

        public void inc() {
            lock.lock();
            try {
                count++;
            } finally {
                lock.unlock();
            }
        }

        public long get() {
            return count;
        }
    }
}

   上述代碼執行以下:

finished : 5
finished : 3
finished : 1
finished : 8
finished : 0
finished : 6
finished : 4
finished : 2
finished : 7
finished : 9
both have finished....
SafeSeqWithLock:100000

   上述代碼操做中,經過對inc操做加鎖,保證了線程安全。

  concurrent包裏面還提供了一個很是有用的鎖,讀寫鎖ReadWriteLock。  

A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. 
The read lock may be held simultaneously by multiple reader threads, so long as there are no writers.
The write lock is exclusive.

   上述英文意思是說:讀鎖能夠有不少個鎖同時上鎖,只要當前沒有寫鎖; 寫鎖是排他的,上了寫鎖,其餘線程既不能上讀鎖,也不能上寫鎖;一樣,須要上寫鎖的前提是既沒有讀鎖,也沒有寫鎖。  

package com.itszt.test1;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 測試讀寫鎖
 */
public class RWLockTest {
    public static void main(String[] args) {
        RWLockTest lockTest = new RWLockTest();
        lockTest.testRWLock_getw_onr();
    }

    public void testRWLock_getw_onr() {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        final Lock rlock = lock.readLock();
        final Lock wlock = lock.writeLock();
        final CountDownLatch l = new CountDownLatch(2);
        // start r thread,開啓讀鎖
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " now to get rlock--獲取讀鎖");
                rlock.lock();
                try {
                    Thread.currentThread().sleep(2 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " now to unlock rlock--釋放讀鎖");
                rlock.unlock();
                l.countDown();
            }
        }).start();
        // start w thread,開啓寫鎖
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " now to get wlock--獲取寫鎖");
                wlock.lock();
                System.out.println(Thread.currentThread().getName() + " now to unlock wlock--釋放寫鎖");
                wlock.unlock();
                l.countDown();
            }
        }).start();
        try {
            l.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " finished");
    }
}

   上述代碼執行以下:

Thread-0 now to get rlock--獲取讀鎖
Thread-1 now to get wlock--獲取寫鎖
Thread-0 now to unlock rlock--釋放讀鎖
Thread-1 now to unlock wlock--釋放寫鎖
main finished

   ReadWriteLock的實現是ReentrantReadWriteLock,有趣的是,在一個線程中,讀鎖不能直接升級爲寫鎖,可是寫鎖能夠降級爲讀鎖;這意思是說,若是你已經有了讀鎖,再去試圖得到寫鎖,將會沒法得到, 一直堵住了;可是若是你有了寫鎖,再去試圖得到讀鎖,就沒問題。

  下面是一段寫鎖降級的代碼:

public void testRWLock_downgrade() {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        Lock rlock = lock.readLock();
        Lock wlock = lock.writeLock();
        System.out.println("now to get wlock");
        wlock.lock();
        System.out.println("now to get rlock");
        rlock.lock();
        System.out.println("now to unlock wlock");
        wlock.unlock();
        System.out.println("now to unlock rlock");
        rlock.unlock();
        System.out.println("finished");
    }

   上述代碼在main函數中執行後,結果以下:

now to get wlock
now to get rlock
now to unlock wlock
now to unlock rlock
finished

   咱們再看一段讀鎖升級的代碼:

public void testRWLock_upgrade() {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        Lock rlock = lock.readLock();
        Lock wlock = lock.writeLock();
        System.out.println("now to get rlock");
        rlock.lock();
        System.out.println("now to get wlock");
        wlock.lock();
        System.out.println("now to unlock wlock");
        wlock.unlock();
        System.out.println("now to unlock rlock");
        rlock.unlock();
        System.out.println("finished");
    }

   上述代碼執行中,已經有了讀鎖,再去試圖得到寫鎖,將會沒法得到, 程序一直堵塞,進入死鎖狀態,顯示以下:

now to get rlock
now to get wlock

   另外,CountDownLatch是一個同步的輔助類,容許一個或多個線程,等待其餘一組線程完成操做,再繼續執行。
  CyclicBarrier也是一個同步的輔助類,容許一組線程相互之間等待,達到一個共同點,再繼續執行。
  
CountDownLatch和CyclicBarrier都是Synchronization  aid,即「同步輔助器」,既然都是輔助工具,在使用中有什麼區別,各自的使用場景如何?  

CountDownLatch場景舉例:一年級期末考試要開始了,監考老師發下去試卷,而後坐在講臺旁邊玩着手機等待着學生答題,有的學生提早交了試卷,並約起打球了,等到最後一個學生交卷了,老師開始整理試卷,貼封條,下班,陪老婆孩子去了。
啓發:CountDownLatch很像一個倒計時鎖,倒計時結束,另外一個線程纔開始執行。就如監考老師要結束監考工做,必須等待全部學生都交了試卷,監考工做才能進入結束環節。

CyclicBarrier場景舉例:公司組織戶外拓展活動,幫助團隊建設,其中最重要的一個項目就是要求全體員工(包括女同事,BOSS,一個都不能少)都能翻越一個高達四米,並且沒有任何抓點的高牆,才能繼續進行其餘項目。
啓發:CyclicBarrier能夠當作是個障礙,全部的線程必須到齊後才能一塊兒經過這個障礙。   
  另外,concurrent包中線程池部分,請參考個人另外一篇博文《ScheduledThreadExecutor定時任務線程池》
相關文章
相關標籤/搜索