Java中的多線程詳解

轉自:http://www.cnblogs.com/bigbigheart/p/6005583.html
如果對什么是線程、什么是進程仍存有疑惑顷牌,請先Google之帜平,因為這兩個概念不在本文的范圍之內瀑焦。

用多線程只有一個目的蔫浆,那就是更好的利用cpu的資源,因為所有的多線程代碼都可以用單線程來實現软啼。說這個話其實只有一半對桑谍,因為反應“多角色”的程序代碼,最起碼每個角色要給他一個線程吧祸挪,否則連實際場景都無法模擬锣披,當然也沒法說能用單線程來實現:比如最常見的“生產者,消費者模型”贿条。

很多人都對其中的一些概念不夠明確雹仿,如同步、并發(fā)等等整以,讓我們先建立一個數據字典胧辽,以免產生誤會。

多線程:指的是這個程序(一個進程)運行時產生了不止一個線程
并行與并發(fā):
并行:多個cpu實例或者多臺機器同時執(zhí)行一段處理邏輯公黑,是真正的同時邑商。
并發(fā):通過cpu調度算法,讓用戶看上去同時執(zhí)行帆调,實際上從cpu操作層面不是真正的同時奠骄。并發(fā)往往在場景中有公用的資源豆同,那么針對這個公用的資源往往產生瓶頸番刊,我們會用TPS或者QPS來反應這個系統的處理能力。


1.png

線程安全:經常用來描繪一段代碼影锈。指在并發(fā)的情況之下芹务,該代碼經過多線程使用,線程的調度順序不影響任何結果鸭廷。這個時候使用多線程枣抱,我們只需要關注系統的內存,cpu是不是夠用即可辆床。反過來佳晶,線程不安全就意味著線程的調度順序會影響最終結果,如不加事務的轉賬代碼:

void transferMoney(User from, User to, float amount){
  to.setMoney(to.getBalance() + amount);
  from.setMoney(from.getBalance() - amount);
}

同步:Java中的同步指的是通過人為的控制和調度讼载,保證共享資源的多線程訪問成為線程安全轿秧,來保證結果的準確中跌。如上面的代碼簡單加入@synchronized關鍵字。在保證結果準確的同時菇篡,提高性能漩符,才是優(yōu)秀的程序。線程安全的優(yōu)先級高于性能。
好了,讓我們開始吧湿故。我準備分成幾部分來總結涉及到多線程的內容:

扎好馬步:線程的狀態(tài)
內功心法:每個對象都有的方法(機制)
太祖長拳:基本線程類
九陰真經:高級多線程控制類
扎好馬步:線程的狀態(tài)
先來兩張圖:

2.png
3.png

各種狀態(tài)一目了然畔咧,值得一提的是”blocked”這個狀態(tài):
線程在Running的過程中可能會遇到阻塞(Blocked)情況

調用join()和sleep()方法,sleep()時間結束或被打斷父虑,join()中斷,IO完成都會回到Runnable狀態(tài),等待JVM的調度。
調用wait()狐赡,使該線程處于等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool )疟丙,釋放同步鎖使線程回到可運行狀態(tài)(Runnable)
對Running狀態(tài)的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(tài)(Runnable)颖侄。
此外,在runnable狀態(tài)的線程是處于被調度的線程享郊,此時的調度順序是不一定的览祖。Thread類中的yield方法可以讓一個running狀態(tài)的線程轉入runnable。

內功心法:每個對象都有的方法(機制)
synchronized, wait, notify 是任何對象都具有的同步工具炊琉。讓我們先來了解他們

4.png

他們是應用于同步問題的人工線程調度工具展蒂。講其本質,首先就要明確monitor的概念苔咪,Java中的每個對象都有一個監(jiān)視器锰悼,來監(jiān)測并發(fā)代碼的重入。在非多線程編碼時該監(jiān)視器不發(fā)揮作用团赏,反之如果在synchronized 范圍內箕般,監(jiān)視器發(fā)揮作用。

wait/notify必須存在于synchronized塊中舔清。并且丝里,這三個關鍵字針對的是同一個監(jiān)視器(某對象的監(jiān)視器)。這意味著wait之后体谒,其他線程可以進入同步塊執(zhí)行杯聚。

當某代碼并不持有監(jiān)視器的使用權時(如圖中5的狀態(tài),即脫離同步塊)去wait或notify抒痒,會拋出java.lang.IllegalMonitorStateException幌绍。也包括在synchronized塊中去調用另一個對象的wait/notify,因為不同對象的監(jiān)視器不同,同樣會拋出此異常傀广。

再講用法:

synchronized單獨使用:
代碼塊:如下痢虹,在多線程環(huán)境下,synchronized塊中的方法獲取了lock實例的monitor主儡,如果實例相同奖唯,那么只有一個線程能執(zhí)行該塊內容

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();   //通知等待區(qū)的消費者可以取出產品了
  }

  /**
   * 消費者從店員取產品
   */
  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關鍵詞的作用:每次針對該變量的操作都激發(fā)一次load and save)。

5.png

針對多線程使用的變量如果不是volatile或者final修飾的榕栏,很有可能產生不可預知的結果(另一個線程修改了這個值畔勤,但是之后在某線程看到的是修改之前的值)。其實道理上講同一實例的同一屬性本身只有一個副本扒磁。但是多線程是會緩存值的庆揪,本質上,volatile就是不去緩存妨托,直接取值缸榛。在線程安全的情況下加volatile會犧牲性能。

太祖長拳:基本線程類
基本線程類指的是Thread類兰伤,Runnable接口内颗,Callable接口
Thread 類實現了Runnable接口,啟動一個線程的方法:

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

Thread類相關方法:

//當前線程可轉讓cpu控制權敦腔,讓別的就緒狀態(tài)線程運行(切換)
public static Thread.yield()
//暫停一段時間
public static Thread.sleep()
//在一個線程中調用other.join(),將等待other執(zhí)行完后才繼續(xù)本線程均澳。    
public join()
//后兩個函數皆可以被打斷
public interrupte()

關于中斷:它并不像stop方法那樣會中斷一個正在運行的線程会烙。線程會不時地檢測中斷標識位负懦,以判斷線程是否應該被中斷(中斷標識值是否為true)筒捺。終端只會影響到wait狀態(tài)柏腻、sleep狀態(tài)和join狀態(tài)。被打斷的線程會拋出InterruptedException系吭。
Thread.interrupted()檢查當前線程是否發(fā)生中斷五嫂,返回boolean
synchronized在獲鎖的過程中是不能被中斷的。

中斷是一個狀態(tài)!interrupt()方法只是將這個狀態(tài)置為true而已沃缘。所以說正常運行的程序不去檢測狀態(tài)躯枢,就不會終止,而wait等阻塞方法會去檢查并拋出異常槐臀。如果在正常運行的程序中添加while(!Thread.interrupted()) 锄蹂,則同樣可以在中斷后離開代碼體

Thread類最佳實踐:
寫的時候最好要設置線程名稱 Thread.name,并設置線程組 ThreadGroup水慨,目的是方便管理得糜。在出現問題的時候,打印線程棧 (jstack -pid) 一眼就可以看出是哪個線程出的問題晰洒,這個線程是干什么的朝抖。

如何獲取線程中的異常

6.png

Runnable
與Thread類似

Callable
future模式:并發(fā)模式的一種,可以有兩種形式谍珊,即無阻塞和阻塞治宣,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態(tài)

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

九陰真經:高級多線程控制類
以上都屬于內功心法,接下來是實際項目中常用到的工具了贝润,Java1.5提供了一個非常高效實用的多線程包:java.util.concurrent, 提供了大量高級工具,可以幫助開發(fā)者編寫高效豌拙、易維護、結構清晰的Java多線程程序题暖。

1.ThreadLocal類
用處:保存線程的獨立變量按傅。對一個線程類(繼承自Thread)
當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本胧卤,所以每一個線程都可以獨立地改變自己的副本唯绍,而不會影響其它線程所對應的副本。常用于用戶登錄控制枝誊,如記錄session信息况芒。

實現:每個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map,功能與map一樣叶撒,區(qū)別是桶里放的是entry而不是entry的鏈表绝骚。功能還是一個map。)以本身為key祠够,以目標為value压汪。
主要方法是get()和set(T a),set之后在map里維護一個threadLocal -> a古瓤,get時將a返回止剖。ThreadLocal是一個特殊的容器腺阳。

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的加十元代碼約為:

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

AtomicReference
對于AtomicReference 來講主届,也許對象會出現,屬性丟失的情況待德,即oldObject == current君丁,但是oldObject.getPropertyA != current.getPropertyA。
這時候将宪,AtomicStampedReference就派上用場了绘闷。這也是一個很常用的思路,即加上版本號

3.Lock類 
lock: 在java.util.concurrent包內较坛。共有三個實現:

ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
主要目的是和synchronized一樣印蔗, 兩者都是為了解決同步問題,處理資源爭端而產生的技術丑勤。功能類似但有一些區(qū)別华嘹。

區(qū)別如下:

lock更靈活,可以自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的后解順序)
提供多種加鎖方案法竞,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式耙厚, 還有trylock的帶超時時間版本。
本質上和監(jiān)視器鎖(即synchronized是一樣的)
能力越大岔霸,責任越大薛躬,必須控制好加鎖和解鎖,否則會導致災難呆细。
和Condition類的結合型宝。
性能更高,對比如下圖:
ReentrantLock    
可重入的意義在于持有鎖的線程可以繼續(xù)持有絮爷,并且要釋放對等的次數后才真正釋放該鎖趴酣。
使用方法是:

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是可以信任的:經過測試贯要,哪怕是發(fā)生了OutofMemoryError暖侨,finally塊中的語句執(zhí)行也能夠得到保證。

ReentrantReadWriteLock

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

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

兩者都有l(wèi)ock,unlock方法崇渗。寫寫字逗,寫讀互斥;讀讀不互斥宅广『簦可以實現并發(fā)讀的高效線程安全代碼

4.容器類
這里就討論比較常用的兩個:

BlockingQueue
ConcurrentHashMap
BlockingQueue
阻塞隊列。該類是java.util.concurrent包下的重要類跟狱,通過對Queue的學習可以得知俭厚,這個queue是單向隊列,可以在隊列頭添加元素和在隊尾刪除或取出元素驶臊。類似于一個管  道挪挤,特別適用于先進先出策略的一些應用場景。普通的queue接口主要實現有PriorityQueue(優(yōu)先隊列)关翎,有興趣可以研究

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

8.png

除了傳統的queue功能(表格左邊的兩列)之外电禀,還提供了阻塞接口put和take,帶超時功能的阻塞接口offer和poll笤休。put會在隊列滿的時候阻塞尖飞,直到有空間時被喚醒;take在隊 列空的時候阻塞店雅,直到有東西拿的時候才被喚醒政基。用于生產者-消費者模型尤其好用,堪稱神器闹啦。

常見的阻塞隊列有:

ArrayListBlockingQueue 
LinkedListBlockingQueue 
DelayQueue 
SynchronousQueue 
ConcurrentHashMap 
高效的線程安全哈希map沮明。請對比hashTable , concurrentHashMap, HashMap

5.管理類
管理類的概念比較泛,用于管理線程窍奋,本身不是多線程的荐健,但提供了一些機制來利用上述的工具做一些封裝酱畅。
了解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統級管理類 ThreadMXBean
ThreadPoolExecutor
如果不了解這個類,應該了解前面提到的ExecutorService江场,開一個自己的線程池非常方便:

ExecutorService e = Executors.newCachedThreadPool();
    ExecutorService e = Executors.newSingleThreadExecutor();
    ExecutorService e = Executors.newFixedThreadPool(3);
    // 第一種是可變大小線程池纺酸,按照任務數來分配線程,
    // 第二種是單線程池址否,相當于FixedThreadPool(1)
    // 第三種是固定大小線程池餐蔬。
    // 然后運行
    e.execute(new MyRunnableImpl());

該類內部是通過ThreadPoolExecutor實現的,掌握該類有助于理解線程池的管理佑附,本質上樊诺,他們都是ThreadPoolExecutor類的各種實現版本。請參見javadoc:

9.png

翻譯一下:
corePoolSize:池內線程初始值與最小值音同,就算是空閑狀態(tài)词爬,也會保持該數量線程。
maximumPoolSize:線程最大值权均,線程的增長始終不會超過該值顿膨。
keepAliveTime:當池內線程數高于corePoolSize時,經過多少時間多余的空閑線程才會被回收螺句∷洳眩回收前處于wait狀態(tài)
unit:
時間單位,可以使用TimeUnit的實例蛇尚,如TimeUnit.MILLISECONDS 
workQueue:待入任務(Runnable)的等待場所芽唇,該參數主要影響調度策略,如公平與否取劫,是否產生餓死(starving)
threadFactory:線程工廠類匆笤,有默認實現,如果有自定義的需要則需要自己實現ThreadFactory接口并作為參數傳入谱邪。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末炮捧,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子惦银,更是在濱河造成了極大的恐慌咆课,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扯俱,死亡現場離奇詭異书蚪,居然都是意外死亡,警方通過查閱死者的電腦和手機迅栅,發(fā)現死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門殊校,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人读存,你說我怎么就攤上這事为流∨皇海” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵敬察,是天一觀的道長秀睛。 經常有香客問我,道長静汤,這世上最難降的妖魔是什么琅催? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任居凶,我火速辦了婚禮虫给,結果婚禮上,老公的妹妹穿的比我還像新娘侠碧。我一直安慰自己抹估,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布弄兜。 她就那樣靜靜地躺著药蜻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪替饿。 梳的紋絲不亂的頭發(fā)上语泽,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音视卢,去河邊找鬼踱卵。 笑死,一個胖子當著我的面吹牛据过,可吹牛的內容都是我干的惋砂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼绳锅,長吁一口氣:“原來是場噩夢啊……” “哼西饵!你這毒婦竟也來了?” 一聲冷哼從身側響起鳞芙,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤眷柔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后原朝,有當地人在樹林里發(fā)現了一具尸體驯嘱,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年竿拆,在試婚紗的時候發(fā)現自己被綠了宙拉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡丙笋,死狀恐怖谢澈,靈堂內的尸體忽然破棺而出煌贴,到底是詐尸還是另有隱情,我是刑警寧澤锥忿,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布牛郑,位于F島的核電站,受9級特大地震影響敬鬓,放射性物質發(fā)生泄漏淹朋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一钉答、第九天 我趴在偏房一處隱蔽的房頂上張望础芍。 院中可真熱鬧,春花似錦数尿、人聲如沸仑性。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诊杆。三九已至,卻和暖如春何陆,著一層夾襖步出監(jiān)牢的瞬間晨汹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工贷盲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淘这,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓晃洒,卻偏偏與公主長得像慨灭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子球及,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容