多線程

java有一些重點的技術,非初學者都必須掌握的。如下,就是我關於這些知識的交流和分享。java

  • Spring的IOC(依賴注入和控制反轉)

依賴注入核心解決的問題:依賴注入的思想是不去主動得到你須要的東西,而是相反,你本身做爲一種服務,讓須要的東西來找你。該模式的核心是將一個服務與它所依賴的其餘服務解耦,這樣一來,那些依賴能夠替換爲測試用的mock對象,或者針對其餘環境替換爲恰當的變體。經過保持對於所依賴對象的不可知性,一個服務是高內聚的,功能專注的而且易於進化。面試

依賴注入經常使用的三種方式。1.set添加依賴的對象。2.構造方法帶入依賴的對象。3.實例工廠的方式注入對象(工廠模式中常見,依賴接口或者抽象類,具體對象根據工廠模式獲取)。spring

示例場景:客戶端須要使用一個用戶類,用戶類中依賴用戶信息類。分別從傳統和依賴注入兩種不一樣思想下程序的實現過程。編程

傳統場景下:緩存

clipboard

以上能夠看出,客戶端類在程序的運行中佔據主導地位。建立者(客戶端類)控制着整個被建立的類生成和組織。安全

依賴注入思想下spring容器的操做session

clipboard

業務控制類(客戶端類)將控制權交出給IoC容器,本身也被IoC容器控制(生命週期等),控制發生的反轉。多線程

換個角度看待問題,傳統是誰須要誰創造誰控制,如今變成有統一的控制容器控制,其實就是分工明細,擅長的人作擅長的事情(IoC容器管理控制),業務高度內聚。告別小做坊,原本就是一個先進的理念。併發

IoC 不是一種技術,只是一種思想,一個重要的面向對象編程的法則。使用IoC容器後,把建立和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,因此對象與對象之間是 鬆散耦合,這樣也方便 測試,利於功能複用,更重要的是使得程序的整個體系結構變得很是靈活。app

  • 反射:

有人說,若是把java中的各項技術比做一門武功的話,那麼反射就是易筋經之類的武功。一旦學會就等於打通了任督二脈,對不少技術的瞭解更加的容易。學習其它技術也變得簡單。我承認這樣的理解。

由於反射,讓java從靜態變成了動態語言。再也不是提早寫好代碼,運行就是按照業務流程流動。反射改變整個處理過程。在運行的過程當中動態的加載實體類。好比spring的動態加載bean,spring的AOP面向切面的動態代理。還有在實際工做中關於反射的使用,讓代碼變得很靈活。讓人感到神來之筆。

下面,就分別從spring動態加載bean的實例,springAOP動態代理的實例和實際工做(excel導入)的實例來詳細分析反射機制。實例代碼以下:

  1. spring動態加載bean:
  2. springAOP的動態代理:
  3. 數據導入的動態代理實現(萬能excel導入/導出):

獲取class的三種方式

  1. // 第一種方式 Class  靜態方法forName - 獲取類對象

          try {

            demo = Class.forName("com.maop.rf.bean.Book");

          } catch (Exception e) {

            e.printStackTrace();

          }

          System.out.println(demo);

     

        2.  // 第二種 經過實例化對象來獲取類對象

          Book bo = new Book();

          Object ob = bo;

          System.out.println("第二種 " + ob.getClass());

     

        3.  // 第三種 直接使用類名點class

          demo2 = Book.class;

          System.out.println("第三種:" + demo2);

     

          try {

            Book bo1 = (Book) demo2.newInstance();

            System.out.println(bo1);

          } catch (Exception e) {

            // TODO: handle exception

          }

  • 多線程:

多線程是中級的開發人員都必須掌握的基本技術。基本上面試都會遇到這方面的問題。

  1. 概念和原理:

進程是指一個內存中運行的應用程序,每一個進程都有本身獨立的一塊內存空間,一個進程中能夠啓動多個線程。

線程是指進程中的一個執行流程,一個進程中能夠運行多個線程。線程老是屬於某個進程,進程中的多個線程共享進程的內存。

使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啓動新線程。

線程整體分兩類:用戶線程和守候線程

  1. 建立與啓動

1、定義線程

一、擴展java.lang.Thread類。

此類中有個run()方法,應該注意其用法:

public void run()

若是該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;不然,該方法不執行任何操做並返回。

Thread 的子類應該重寫該方法。

二、實現java.lang.Runnable接口。

void run()

使用實現接口 Runnable 的對象建立一個線程時,啓動該線程將致使在獨立執行的線程中調用對象的 run 方法。

方法 run 的常規協定是,它可能執行任何所需的操做。

兩種方式的區別:既然都能實現線程的功能,怎樣區別使用呢。java規定只能單繼承,若是自定義類須要繼承其餘類,只能選擇實現Runnable接口。

2、實例化線程

一、若是是擴展java.lang.Thread類的線程,則直接new便可。

二、若是是實現了java.lang.Runnable接口的類,則用Thread的構造方法:

Thread(Runnable target)

Thread(Runnable target, String name)

3、啓動線程

在線程的Thread對象上調用start()方法,而不是run()或者別的方法。

4、例子

一、實現Runnable接口的多線程例子

/**

* 實現Runnable接口的類

*

* @author

*/

public class DoSomething implements Runnable {

private String name;

public DoSomething(String name) {

this.name = name;

    }

public void run() {

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

for (long k = 0; k < 100000000; k++) ;

            System.out.println(name + ": " + i);

        }

    }

}

/**

* 測試Runnable類實現的多線程程序

*

* @author leizhimin 2008-9-13 18:15:02

*/

public class TestRunnable {

public static void main(String[] args) {

        DoSomething ds1 = new DoSomething("阿三");

        DoSomething ds2 = new DoSomething("李四");

        Thread t1 = new Thread(ds1);

        Thread t2 = new Thread(ds2);

        t1.start();

        t2.start();

    }

}

二、擴展Thread類實現的多線程例子

/**

* 測試擴展Thread類實現的多線程程序

*

* @author leizhimin

*/

public class TestThread extends Thread{

public TestThread(String name) {

super(name);

    }

public void run() {

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

for(long k= 0; k <100000000;k++);

            System.out.println(this.getName()+" :"+i);

        }

    }

public static void main(String[] args) {

        Thread t1 = new TestThread("阿三");

        Thread t2 = new TestThread("李四");

        t1.start();

        t2.start();

    }

}

  1. 線程狀態的轉換

線程的狀態轉換是線程控制的基礎。線程狀態總的可分爲五大狀態:分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述以下:

clipboard

新建狀態(New):當線程對象對建立後,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經作好了準備,隨時等待CPU調度執行,並非說執行了t.start()此線程當即就會執行;

運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就     緒狀態是進入到運行狀態的惟一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於運行狀態中的線程因爲某種緣由,暫時放棄對CPU的使用權,中止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的緣由不一樣,阻塞狀態又能夠分爲三種:

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;

2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(由於鎖被其它線程所佔用),它會進入同步阻塞狀態;

3.其餘阻塞 -- 經過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。

死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

線程在Running的過程當中可能會遇到阻塞(Blocked)狀況

  1. 調用join()和sleep()方法,sleep()時間結束或被打斷,join()中斷,IO完成都會回到Runnable狀態,等待JVM的調度。
  2. 調用wait(),使該線程處於等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(Runnable)
  3. 對Running狀態的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(Runnable)。

此外,在runnable狀態的線程是處於被調度的線程,此時的調度順序是不必定的。Thread類中的yield方法可讓一個running狀態的線程轉入runnable。

clipboard

221320062031

  1. 內功心法:每一個對象都有的方法(機制)

synchronized, wait, notify 是任何對象都具備的同步工具。讓咱們先來了解他們

0771d68cb2ba

monitor

他們是應用於同步問題的人工線程調度工具。講其本質,首先就要明確monitor的概念,Java中的每一個對象都有一個監視器,來監測併發代碼的重入。在非多線程編碼時該監視器不發揮做用,反之若是在synchronized 範圍內,監視器發揮做用。

wait/notify必須存在於synchronized塊中。而且,這三個關鍵字針對的是同一個監視器(某對象的監視器)。這意味着wait以後,其餘線程能夠進入同步塊執行。

當某代碼並不持有監視器的使用權時(如圖中5的狀態,即脫離同步塊)去wait或notify,會拋出java.lang.IllegalMonitorStateException。也包括在synchronized塊中去調用另外一個對象的wait/notify,由於不一樣對象的監視器不一樣,一樣會拋出此異常。

再講用法:

  • synchronized單獨使用:
    • 代碼塊:以下,在多線程環境下,synchronized塊中的方法獲取了lock實例的monitor,若是實例相同,那麼只有一個線程能執行該塊內容

public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } } }

直接用於方法: 至關於上面代碼中用lock來鎖定的效果,實際獲取的是Thread1類的monitor。更進一步,若是修飾的是static方法,則鎖定該類全部實例。

public class Thread1 implements Runnable { public synchronized void run() { ..do something } }

  • synchronized, wait, notify結合:典型場景生產者消費者問題

/** * 生產者生產出來的產品交給店員 */ public synchronized void produce() { if(this.product >= MAX_PRODUCT) { try { wait(); System.out.println("產品已滿,請稍候再生產"); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println("生產者生產第" + this.product + "個產品."); notifyAll(); //通知等待區的消費者能夠取出產品了 } /** * 消費者從店員取產品 */ public synchronized void consume() { if(this.product <= MIN_PRODUCT) { try { wait(); System.out.println("缺貨,稍候再取"); } catch (InterruptedException e) { e.printStackTrace(); } return; } System.out.println("消費者取走了第" + this.product + "個產品."); this.product--; notifyAll(); //通知等待去的生產者能夠生產產品了 }

volatile

多線程的內存模型:main memory(主存)、working memory(線程棧),在處理數據時,線程會把值從主存load到本地棧,完成操做後再save回去(volatile關鍵詞的做用:每次針對該變量的操做都激發一次load and save)。

6cfda7042c67

volatile

針對多線程使用的變量若是不是volatile或者final修飾的,頗有可能產生不可預知的結果(另外一個線程修改了這個值,可是以後在某線程看到的是修改以前的值)。其實道理上講同一實例的同一屬性自己只有一個副本。可是多線程是會緩存值的,本質上,volatile就是不去緩存,直接取值。在線程安全的狀況下加volatile會犧牲性能。

  1. 太祖長拳:基本線程類

基本線程類指的是Thread類,Runnable接口,Callable接口

Thread 類實現了Runnable接口,啓動一個線程的方法:

 MyThread my = new MyThread();   my.start();

Thread類相關方法:

copycode

//當前線程可轉讓cpu控制權,讓別的就緒狀態線程運行(切換) public static Thread.yield() //暫停一段時間 public static Thread.sleep() //在一個線程中調用other.join(),將等待other執行完後才繼續本線程。     public join() //後兩個函數皆能夠被打斷 public interrupte()

copycode

關於中斷:它並不像stop方法那樣會中斷一個正在運行的線程。線程會不時地檢測中斷標識位,以判斷線程是否應該被中斷(中斷標識值是否爲true)。終端只會影響到wait狀態、sleep狀態和join狀態。被打斷的線程會拋出InterruptedException。

Thread.interrupted()檢查當前線程是否發生中斷,返回boolean

synchronized在獲鎖的過程當中是不能被中斷的。

中斷是一個狀態!interrupt()方法只是將這個狀態置爲true而已。因此說正常運行的程序不去檢測狀態,就不會終止,而wait等阻塞方法會去檢查並拋出異常。若是在正常運行的程序中添加while(!Thread.interrupted()) ,則一樣能夠在中斷後離開代碼體

Thread類最佳實踐:

寫的時候最好要設置線程名稱 Thread.name,並設置線程組 ThreadGroup,目的是方便管理。在出現問題的時候,打印線程棧 (jstack -pid) 一眼就能夠看出是哪一個線程出的問題,這個線程是幹什麼的。

如何獲取線程中的異常

5b7f8df6e8d3

不能用try,catch來獲取線程中的異常

Runnable

與Thread相似

Callable

future模式:併發模式的一種,能夠有兩種形式,即無阻塞和阻塞,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態

ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重參數版本,及支持callable也可以支持runnable接口類型. Future future = e.submit(new myCallable()); future.isDone() //return true,false 無阻塞 future.get() // return 返回值,阻塞直到該線程運行結束

  1. 九陰真經:高級多線程控制類

包:java.util.concurrent, 提供了大量高級工具,能夠幫助開發者編寫高效、易維護、結構清晰的Java多線程程序。

6.1.ThreadLocal類

用處:保存線程的獨立變量。對一個線程類(繼承自Thread)

當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。經常使用於用戶登陸控制,如記錄session信息。

實現:每一個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map,功能與map同樣,區別是桶裏放的是entry而不是entry的鏈表。功能仍是一個map。)以自己爲key,以目標爲value。

主要方法是get()和set(T a),set以後在map裏維護一個threadLocal -> a,get時將a返回。ThreadLocal是一個特殊的容器。

6.2.原子類(AtomicInteger、AtomicBoolean……)

若是使用atomic wrapper class如atomicInteger,或者使用本身保證原子的操做,則等同於synchronized

//返回值爲boolean AtomicInteger.compareAndSet(int expect,int update)

該方法可用於實現樂觀鎖,考慮文中最初提到的以下場景:a給b付款10元,a扣了10元,b要加10元。此時c給b2元,可是b的加十元代碼約爲:

copycode

if(b.value.compareAndSet(old, value)){ return ; }else{ //try again // if that fails, rollback and log }

copycode

AtomicReference

對於AtomicReference 來說,也許對象會出現,屬性丟失的狀況,即oldObject == current,可是oldObject.getPropertyA != current.getPropertyA。

這時候,AtomicStampedReference就派上用場了。這也是一個很經常使用的思路,即加上版本號

6.3.Lock類 

lock: 在java.util.concurrent包內。共有三個實現:

ReentrantLock ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock

主要目的是和synchronized同樣, 二者都是爲了解決同步問題,處理資源爭端而產生的技術。功能相似但有一些區別。

區別以下:

copycode

lock更靈活,能夠自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的後解順序)提供多種加鎖方案,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有trylock的帶超時時間版本。本質上和監視器鎖(即synchronized是同樣的)能力越大,責任越大,必須控制好加鎖和解鎖,不然會致使災難。和Condition類的結合。性能更高,對好比下圖:

copycode

93b31bfed934

synchronized和Lock性能對比

ReentrantLock

可重入的意義在於持有鎖的線程能夠繼續持有,而且要釋放對等的次數後才真正釋放該鎖。

使用方法是:

1.先new一個實例

static ReentrantLock r=new ReentrantLock();

2.加鎖      

r.lock()或r.lockInterruptibly();

此處也是個不一樣,後者可被打斷。當a線程lock後,b線程阻塞,此時若是是lockInterruptibly,那麼在調用b.interrupt()以後,b線程退出阻塞,並放棄對資源的爭搶,進入catch塊。(若是使用後者,必須throw interruptable exception 或catch)    

3.釋放鎖   

r.unlock()

必須作!何爲必須作呢,要放在finally裏面。以防止異常跳出了正常流程,致使災難。這裏補充一個小知識點,finally是能夠信任的:通過測試,哪怕是發生了OutofMemoryError,finally塊中的語句執行也可以獲得保證。

ReentrantReadWriteLock

可重入讀寫鎖(讀寫鎖的一個實現)

 ReentrantReadWriteLock lock = new ReentrantReadWriteLock()   ReadLock r = lock.readLock();   WriteLock w = lock.writeLock();

二者都有lock,unlock方法。寫寫,寫讀互斥;讀讀不互斥。能夠實現併發讀的高效線程安全代碼

6.4.容器類

這裏就討論比較經常使用的兩個:

BlockingQueue ConcurrentHashMap

BlockingQueue

阻塞隊列。該類是java.util.concurrent包下的重要類,經過對Queue的學習能夠得知,這個queue是單向隊列,能夠在隊列頭添加元素和在隊尾刪除或取出元素。相似於一個管  道,特別適用於先進先出策略的一些應用場景。普通的queue接口主要實現有PriorityQueue(優先隊列),有興趣能夠研究

BlockingQueue在隊列的基礎上添加了多線程協做的功能:

7b06b8d86db8

BlockingQueue

除了傳統的queue功能(表格左邊的兩列)以外,還提供了阻塞接口put和take,帶超時功能的阻塞接口offer和poll。put會在隊列滿的時候阻塞,直到有空間時被喚醒;take在隊 列空的時候阻塞,直到有東西拿的時候才被喚醒。用於生產者-消費者模型尤爲好用,堪稱神器。

常見的阻塞隊列有:

ArrayListBlockingQueue LinkedListBlockingQueue DelayQueue SynchronousQueue

ConcurrentHashMap

高效的線程安全哈希map。請對比hashTable , concurrentHashMap, HashMap

6.5.管理類

管理類的概念比較泛,用於管理線程,自己不是多線程的,但提供了一些機制來利用上述的工具作一些封裝。

瞭解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統級管理類 ThreadMXBean

ThreadPoolExecutor

若是不瞭解這個類,應該瞭解前面提到的ExecutorService,開一個本身的線程池很是方便:

copycode

ExecutorService e = Executors.newCachedThreadPool(); ExecutorService e = Executors.newSingleThreadExecutor(); ExecutorService e = Executors.newFixedThreadPool(3); // 第一種是可變大小線程池,按照任務數來分配線程, // 第二種是單線程池,至關於FixedThreadPool(1) // 第三種是固定大小線程池。 // 而後運行 e.execute(new MyRunnableImpl());

copycode

該類內部是經過ThreadPoolExecutor實現的,掌握該類有助於理解線程池的管理,本質上,他們都是ThreadPoolExecutor類的各類實現版本。請參見javadoc:

8a70ba646843

ThreadPoolExecutor參數解釋

corePoolSize:池內線程初始值與最小值,就算是空閒狀態,也會保持該數量線程。 maximumPoolSize:線程最大值,線程的增加始終不會超過該值。 keepAliveTime:當池內線程數高於corePoolSize時,通過多少時間多餘的空閒線程纔會被回收。回收前處於wait狀態 unit:時間單位,可使用TimeUnit的實例,如TimeUnit.MILLISECONDS  workQueue:待入任務(Runnable)的等待場所,該參數主要影響調度策略,如公平與否,是否產生餓死(starving) threadFactory:線程工廠類,有默認實現,若是有自定義的須要則須要本身實現ThreadFactory接口並做爲參數傳入。

copycode

  1. 知識點
  • 線程使用run方法調用不會建立新的線程 ,使用start調用會產生新的線程
  • sleep和wait的區別:
  • sleep是Thread類的方法,wait是Object類中定義的方法.
  • Thread.sleep不會致使鎖行爲的改變, 若是當前線程是擁有鎖的, 那麼Thread.sleep不會讓線程釋放鎖.
  • Thread.sleep和Object.wait都會暫停當前的線程. OS會將執行時間分配給其它線程. 區別是, 調用wait後, 須要別的線程執行notify/notifyAll纔可以從新得到CPU執行時間.
  • 線程同步以及線程調度相關的方法
    • wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
    • sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
    • notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
    • notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;
  • 線程的sleep()方法和yield()方法有什麼區別?

① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行 的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;

② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;

  • 如何在Java中實現線程?

建立線程有兩種方式:

1、繼承 Thread 類,擴展線程。

2、實現 Runnable 接口。

  • 線程和進程有什麼區別?

一個進程是一個獨立(self contained)的運行環境,它能夠被看做一個程序或者一個應用。而線程是在進程中執行的一個任務。線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。

  1. 線程簡單介紹

線程的特徵:輕量,獨立,共享(共享進程中的資源)。

線程的實現:1.繼承thread類,從新run方法。2.實現runnable接口,實現run方法。

線程的生命週期:新建,就緒,運行,阻塞,死亡。

常見API:

靜態方法:currentThread():獲取當前運行線程引用。yield:自動進入就緒狀態,放棄時間片。sleep休眠多長時間後進入就緒狀態。

實例方法:start(啓動) setname(線程名)

  1. 線程的同步(協同,有序)

1.對象鎖,同步塊。(同步區域--粒度更小)

2.同步方法。

// 第一種方式 Class 的 靜態方法forName - 獲取類對象try {demo = Class.forName("com.maop.rf.bean.Book");} catch (Exception e) {e.printStackTrace();}System.out.println(demo);// 第二種 經過實例化對象來獲取類對象Book bo = new Book();Object ob = bo;System.out.println("第二種 :" + ob.getClass());// 第三種 直接使用類名點classdemo2 = Book.class;System.out.println("第三種:" + demo2);try {Book bo1 = (Book) demo2.newInstance();System.out.println(bo1);} catch (Exception e) {// TODO: handle exception}

相關文章
相關標籤/搜索