Java多執行緒學習(吐血超詳細總結)
林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka
寫在前面的話:此文只能說是java多執行緒的一個入門,其實Java裡頭執行緒完全可以寫一本書了,但是如果最基本的你都學掌握好,又怎麼能更上一個臺階呢?如果你覺得此文很簡單,那推薦你看看Java併發包的的執行緒池(Java併發程式設計與技術內幕:執行緒池深入理解),或者看這個專欄:Java併發程式設計與技術內幕。你將會對Java裡頭的高併發場景下的執行緒有更加深刻的理解。
- 一擴充套件javalangThread類
- 二實現javalangRunnable介面
- 三Thread和Runnable的區別
- 四執行緒狀態轉換
- 五執行緒排程
- 六常用函式說明
- 七常見執行緒名詞解釋
- 八執行緒同步
- 九執行緒資料傳遞
本文主要講了java中多執行緒的使用方法、執行緒同步、執行緒資料傳遞、執行緒狀態及相應的一些執行緒函式用法、概述等。在這之前,首先讓我們來了解下在作業系統中程式和執行緒的區別:
程式:每個程式都有獨立的程式碼和資料空間(程式上下文),程式間的切換會有較大的開銷,一個程式包含1--n個執行緒。(程式是資源分配的最小單位)
執行緒:同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小。(執行緒是cpu排程的最小單位)
執行緒和程式一樣分為五個階段:建立、就緒、執行、阻塞、終止。
多程式是指作業系統能同時執行多個任務(程式)。
多執行緒是指在同一程式中有多個順序流在執行。
在java中要想實現多執行緒,有兩種手段,一種是繼續Thread類,另外一種是實現Runable介面.(其實準確來講,應該有三種,還有一種是實現Callable介面,並與Future、執行緒池結合使用,此文這裡不講這個,有興趣看這裡Java併發程式設計與技術內幕:Callable、Future、FutureTask、CompletionService )
一、擴充套件java.lang.Thread類
這裡繼承Thread類的方法是比較常用的一種,如果說你只是想起一條執行緒。沒有什麼其它特殊的要求,那麼可以使用Thread.(筆者推薦使用Runable,後頭會說明為什麼)。下面來看一個簡單的例項
package com.multithread.learning;
/**
*@functon 多執行緒學習
*@author 林炳文
*@time 2015.3.9
*/
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "執行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
}
}
輸出:A執行 : 0
B執行 : 0
A執行 : 1
A執行 : 2
A執行 : 3
A執行 : 4
B執行 : 1
B執行 : 2
B執行 : 3
B執行 : 4
再執行一下:
A執行 : 0B執行 : 0
B執行 : 1
B執行 : 2
B執行 : 3
B執行 : 4
A執行 : 1
A執行 : 2
A執行 : 3
A執行 : 4
但是start方法重複呼叫的話,會出現java.lang.IllegalThreadStateException異常。
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=mTh1;
mTh1.start();
mTh2.start();
輸出:
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Unknown Source)
at com.multithread.learning.Main.main(Main.java:31)
A執行 : 0
A執行 : 1
A執行 : 2
A執行 : 3
A執行 : 4
二、實現java.lang.Runnable介面
採用Runnable也是非常常見的一種,我們只需要重寫run方法即可。下面也來看個例項。
/**
*@functon 多執行緒學習
*@author 林炳文
*@time 2015.3.9
*/
package com.multithread.runnable;
class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "執行 : " + i);
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Thread2("C")).start();
new Thread(new Thread2("D")).start();
}
}
輸出:C執行 : 0
D執行 : 0
D執行 : 1
C執行 : 1
D執行 : 2
C執行 : 2
D執行 : 3
C執行 : 3
D執行 : 4
C執行 : 4
三、Thread和Runnable的區別
如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable介面的話,則很容易的實現資源共享。
總結:
實現Runnable介面比繼承Thread類所具有的優勢:
1):適合多個相同的程式程式碼的執行緒去處理同一個資源
2):可以避免java中的單繼承的限制
3):增加程式的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立
4):執行緒池只能放入實現Runable或callable類執行緒,不能直接放入繼承Thread的類
提醒一下大家:main方法其實也是一個執行緒。在java中所以的執行緒都是同時啟動的,至於什麼時候,哪個先執行,完全看誰先得到CPU的資源。
在java中,每次程式執行至少啟動2個執行緒。一個是main執行緒,一個是垃圾收集執行緒。因為每當使用java命令執行一個類的時候,實際上都會啟動一個JVM,每一個jVM實習在就是在作業系統中啟動了一個程式。
四、執行緒狀態轉換
五、執行緒排程
執行緒的排程
static int MAX_PRIORITY
執行緒可以具有的最高優先順序,取值為10。
static int MIN_PRIORITY
執行緒可以具有的最低優先順序,取值為1。
static int NORM_PRIORITY
分配給執行緒的預設優先順序,取值為5。
六、常用函式說明
①sleep(long millis): 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)②join():指等待t執行緒終止。
使用方式。
join是Thread類的一個方法,啟動執行緒後直接呼叫,即join()的作用是:“等待該執行緒終止”,這裡需要理解的就是該執行緒是指的主執行緒等待子執行緒的終止。也就是在子執行緒呼叫了join()方法後面的程式碼,只有等到子執行緒結束了才能執行。
Thread t = new AThread(); t.start(); t.join();
為什麼要用join()方法
在很多情況下,主執行緒生成並起動了子執行緒,如果子執行緒裡要進行大量的耗時的運算,主執行緒往往將於子執行緒之前結束,但是如果主執行緒處理完其他的事務後,需要用到子執行緒的處理結果,也就是主執行緒需要等待子執行緒執行完成之後再結束,這個時候就要用到join()方法了。
不加join。/**
*@functon 多執行緒學習,join
*@author 林炳文
*@time 2015.3.9
*/
package com.multithread.join;
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
super(name);
this.name=name;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " 執行緒執行開始!");
for (int i = 0; i < 5; i++) {
System.out.println("子執行緒"+name + "執行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 執行緒執行結束!");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主執行緒執行開始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+ "主執行緒執行結束!");
}
}
輸出結果:main主執行緒執行開始!
main主執行緒執行結束!
B 執行緒執行開始!
子執行緒B執行 : 0
A 執行緒執行開始!
子執行緒A執行 : 0
子執行緒B執行 : 1
子執行緒A執行 : 1
子執行緒A執行 : 2
子執行緒A執行 : 3
子執行緒A執行 : 4
A 執行緒執行結束!
子執行緒B執行 : 2
子執行緒B執行 : 3
子執行緒B執行 : 4
B 執行緒執行結束!
發現主執行緒比子執行緒早結束
加join
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主執行緒執行開始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
try {
mTh1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
mTh2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主執行緒執行結束!");
}
}
執行結果:
main主執行緒執行開始!
A 執行緒執行開始!
子執行緒A執行 : 0
B 執行緒執行開始!
子執行緒B執行 : 0
子執行緒A執行 : 1
子執行緒B執行 : 1
子執行緒A執行 : 2
子執行緒B執行 : 2
子執行緒A執行 : 3
子執行緒B執行 : 3
子執行緒A執行 : 4
子執行緒B執行 : 4
A 執行緒執行結束!
主執行緒一定會等子執行緒都結束了才結束
③yield():暫停當前正在執行的執行緒物件,並執行其他執行緒。
/**
*@functon 多執行緒學習 yield
*@author 林炳文
*@time 2015.3.9
*/
package com.multithread.yield;
class ThreadYield extends Thread{
public ThreadYield(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 當i為30時,該執行緒就會把CPU時間讓掉,讓其他或者自己的執行緒執行(也就是誰先搶到誰執行)
if (i ==30) {
this.yield();
}
}
}
}
public class Main {
public static void main(String[] args) {
ThreadYield yt1 = new ThreadYield("張三");
ThreadYield yt2 = new ThreadYield("李四");
yt1.start();
yt2.start();
}
}
執行結果:
第一種情況:李四(執行緒)當執行到30時會CPU時間讓掉,這時張三(執行緒)搶到CPU時間並執行。
第二種情況:李四(執行緒)當執行到30時會CPU時間讓掉,這時李四(執行緒)搶到CPU時間並執行。
sleep()和yield()的區別):sleep()使當前執行緒進入停滯狀態,所以執行sleep()的執行緒在指定的時間內肯定不會被執行;yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行。
sleep 方法使當前執行中的執行緒睡眼一段時間,進入不可執行狀態,這段時間的長短是由程式設定的,yield 方法使當前執行緒讓出 CPU 佔有權,但讓出的時間是不可設定的。實際上,yield()方法對應瞭如下操作:先檢測當前是否有相同優先順序的執行緒處於同可執行狀態,如有,則把 CPU 的佔有權交給此執行緒,否則,繼續執行原來的執行緒。所以yield()方法稱為“退讓”,它把執行機會讓給了同等優先順序的其他執行緒
另外,sleep 方法允許較低優先順序的執行緒獲得執行機會,但 yield() 方法執行時,當前執行緒仍處在可執行狀態,所以,不可能讓出較低優先順序的執行緒些時獲得 CPU 佔有權。在一個執行系統中,如果較高優先順序的執行緒沒有呼叫 sleep 方法,又沒有受到 I\O 阻塞,那麼,較低優先順序執行緒只能等待所有較高優先順序的執行緒執行結束,才有機會執行。
④setPriority(): 更改執行緒的優先順序。
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
Thread4 t1 = new Thread4("t1"); Thread4 t2 = new Thread4("t2"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY);
⑤interrupt():不要以為它是中斷某個執行緒!它只是線執行緒傳送一箇中斷訊號,讓執行緒在無限等待時(如死鎖時)能丟擲丟擲,從而結束執行緒,但是如果你吃掉了這個異常,那麼這個執行緒還是不會中斷的!
⑥wait()
Obj.wait(),與Obj.notify()必須要與synchronized(Obj)一起使用,也就是wait,與notify是針對已經獲取了Obj鎖進行操作,從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內。從功能上來說wait就是說執行緒在獲取物件鎖後,主動釋放物件鎖,同時本執行緒休眠。直到有其它執行緒呼叫物件的notify()喚醒該執行緒,才能繼續獲取物件鎖,並繼續執行。相應的notify()就是對物件鎖的喚醒操作。但有一點需要注意的是notify()呼叫後,並不是馬上就釋放物件鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()物件鎖的執行緒中隨機選取一執行緒,賦予其物件鎖,喚醒執行緒,繼續執行。這樣就提供了線上程間同步、喚醒的操作。Thread.sleep()與Object.wait()二者都可以暫停當前執行緒,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了物件鎖的控制。
單單在概念上理解清楚了還不夠,需要在實際的例子中進行測試才能更好的理解。對Object.wait(),Object.notify()的應用最經典的例子,應該是三執行緒列印ABC的問題了吧,這是一道比較經典的面試題,題目要求如下:
建立三個執行緒,A執行緒列印10次A,B執行緒列印10次B,C執行緒列印10次C,要求執行緒同時執行,交替列印10次ABC。這個問題用Object的wait(),notify()就可以很方便的解決。程式碼如下:
/**
* wait用法
* @author DreamSea
* @time 2015.3.9
*/
package com.multithread.wait;
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //確保按順序A、B、C執行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
輸出結果:
ABCABCABCABCABCABCABCABCABCABC
wait和sleep區別
共同點:
1. 他們都是在多執行緒的環境下,都可以在程式的呼叫處阻塞指定的毫秒數,並返回。
2. wait()和sleep()都可以通過interrupt()方法 打斷執行緒的暫停狀態 ,從而使執行緒立刻丟擲InterruptedException。
如果執行緒A希望立即結束執行緒B,則可以對執行緒B對應的Thread例項呼叫interrupt方法。如果此刻執行緒B正在wait/sleep /join,則執行緒B會立刻丟擲InterruptedException,在catch() {} 中直接return即可安全地結束執行緒。
需要注意的是,InterruptedException是執行緒自己從內部丟擲的,並不是interrupt()方法丟擲的。對某一執行緒呼叫 interrupt()時,如果該執行緒正在執行普通的程式碼,那麼該執行緒根本就不會丟擲InterruptedException。但是,一旦該執行緒進入到 wait()/sleep()/join()後,就會立刻丟擲InterruptedException 。
不同點:
1. Thread類的方法:sleep(),yield()等
Object的方法:wait()和notify()等
2. 每個物件都有一個鎖來控制同步訪問。Synchronized關鍵字可以和物件的鎖互動,來實現執行緒的同步。
sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他執行緒可以使用同步控制塊或者方法。
3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裡面使用,而sleep可以在任何地方使用
所以sleep()和wait()方法的最大區別是:
sleep()睡眠時,保持物件鎖,仍然佔有該鎖;
而wait()睡眠時,釋放物件鎖。
但是wait()和sleep()都可以通過interrupt()方法打斷執行緒的暫停狀態,從而使執行緒立刻丟擲InterruptedException(但不建議使用該方法)。
sleep()方法
sleep()使當前執行緒進入停滯狀態(阻塞當前執行緒),讓出CUP的使用、目的是不讓當前執行緒獨自霸佔該程式所獲的CPU資源,以留一定時間給其他執行緒執行的機會;
sleep()是Thread類的Static(靜態)的方法;因此他不能改變物件的機鎖,所以當在一個Synchronized塊中呼叫Sleep()方法是,執行緒雖然休眠了,但是物件的機鎖並木有被釋放,其他執行緒無法訪問這個物件(即使睡著也持有物件鎖)。
在sleep()休眠時間期滿後,該執行緒不一定會立即執行,這是因為其它執行緒可能正在執行而且沒有被排程為放棄執行,除非此執行緒具有更高的優先順序。
wait()方法
wait()方法是Object類裡的方法;當一個執行緒執行到wait()方法時,它就進入到一個和該物件相關的等待池中,同時失去(釋放)了物件的機鎖(暫時失去機鎖,wait(long timeout)超時時間到後還需要返還物件鎖);其他執行緒可以訪問;
wait()使用notify或者notifyAlll或者指定睡眠時間來喚醒當前等待池中的執行緒。
wiat()必須放在synchronized block中,否則會在program runtime時扔出”java.lang.IllegalMonitorStateException“異常。
七、常見執行緒名詞解釋
sleep(): 強迫一個執行緒睡眠N毫秒。
isAlive(): 判斷一個執行緒是否存活。
join(): 等待執行緒終止。
activeCount(): 程式中活躍的執行緒數。
enumerate(): 列舉程式中的執行緒。
currentThread(): 得到當前執行緒。
isDaemon(): 一個執行緒是否為守護執行緒。
setDaemon(): 設定一個執行緒為守護執行緒。(使用者執行緒和守護執行緒的區別在於,是否等待主執行緒依賴於主執行緒結束而結束)
setName(): 為執行緒設定一個名稱。
wait(): 強迫一個執行緒等待。
notify(): 通知一個執行緒繼續執行。
setPriority(): 設定一個執行緒的優先順序。
八、執行緒同步
1、synchronized關鍵字的作用域有二種:
1)是某個物件例項內,synchronized aMethod(){}可以防止多個執行緒同時訪問這個物件的synchronized方法(如果一個物件有多個synchronized方法,只要一個執行緒訪問了其中的一個synchronized方法,其它執行緒不能同時訪問這個物件中任何一個synchronized方法)。這時,不同的物件例項的synchronized方法是不相干擾的。也就是說,其它執行緒照樣可以同時訪問相同類的另一個物件例項中的synchronized方法;
2)是某個類的範圍,synchronized static aStaticMethod{}防止多個執行緒同時訪問這個類中的synchronized static 方法。它可以對類的所有物件例項起作用。
2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/},它的作用域是當前物件;
3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法為synchronized方法;
Java對多執行緒的支援與同步機制深受大家的喜愛,似乎看起來使用了synchronized關鍵字就可以輕鬆地解決多執行緒共享資料同步問題。到底如何?――還得對synchronized關鍵字的作用進行深入瞭解才可定論。
總的說來,synchronized關鍵字可以作為函式的修飾符,也可作為函式內的語句,也就是平時說的同步方法和同步語句塊。如果再細的分類,synchronized可作用於instance變數、object reference(物件引用)、static函式和class literals(類名稱字面常量)身上。
在進一步闡述之前,我們需要明確幾點:
A.無論synchronized關鍵字加在方法上還是物件上,它取得的鎖都是物件,而不是把一段程式碼或函式當作鎖――而且同步方法很可能還會被其他執行緒的物件訪問。
B.每個物件只有一個鎖(lock)與之相關聯。
C.實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。
接著來討論synchronized用到不同地方對程式碼產生的影響:
假設P1、P2是同一個類的不同物件,這個類中定義了以下幾種情況的同步塊或同步方法,P1、P2就都可以呼叫它們。
1. 把synchronized當作函式修飾符時,示例程式碼如下:
Public synchronized void methodAAA()
{
//….
}
這也就是同步方法,那這時synchronized鎖定的是哪個物件呢?它鎖定的是呼叫這個同步方法物件。也就是說,當一個物件P1在不同的執行緒中執行這個同步方法時,它們之間會形成互斥,達到同步的效果。但是這個物件所屬的Class所產生的另一物件P2卻可以任意呼叫這個被加了synchronized關鍵字的方法。
上邊的示例程式碼等同於如下程式碼:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)處的this指的是什麼呢?它指的就是呼叫這個方法的物件,如P1。可見同步方法實質是將synchronized作用於object reference。――那個拿到了P1物件鎖的執行緒,才可以呼叫P1的同步方法,而對P2而言,P1這個鎖與它毫不相干,程式也可能在這種情形下襬脫同步機制的控制,造成資料混亂:(
2.同步塊,示例程式碼如下:
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
這時,鎖就是so這個物件,誰拿到這個鎖誰就可以執行它所控制的那段程式碼。當有一個明確的物件作為鎖時,就可以這樣寫程式,但當沒有明確的物件作為鎖,只是想讓一段程式碼同步時,可以建立一個特殊的instance變數(它得是一個物件)來充當鎖:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變數
Public void methodA()
{
synchronized(lock) { //… }
}
//…..
}
注:零長度的byte陣列物件建立起來將比任何物件都經濟――檢視編譯後的位元組碼:生成零長度的byte[]物件只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
3.將synchronized作用於static 函式,示例程式碼如下:
Class Foo
{
public synchronized static void methodAAA() // 同步的static 函式
{
//….
}
public void methodBBB()
{
synchronized(Foo.class) // class literal(類名稱字面常量)
}
}
程式碼中的methodBBB()方法是把class literal作為鎖的情況,它和同步的static函式產生的效果是一樣的,取得的鎖很特別,是當前呼叫這個方法的物件所屬的類(Class,而不再是由這個Class產生的某個具體物件了)。
記得在《Effective Java》一書中看到過將 Foo.class和 P1.getClass()用於作同步鎖還不一樣,不能用P1.getClass()來達到鎖這個Class的目的。P1指的是由Foo類產生的物件。
可以推斷:如果一個類中定義了一個synchronized的static函式A,也定義了一個synchronized 的instance函式B,那麼這個類的同一物件Obj在多執行緒中分別訪問A和B兩個方法時,不會構成同步,因為它們的鎖都不一樣。A方法的鎖是Obj這個物件,而B的鎖是Obj所屬的那個Class。
總結一下:
1、執行緒同步的目的是為了保護多個執行緒反問一個資源時對資源的破壞。
2、執行緒同步方法是通過鎖來實現,每個物件都有切僅有一個鎖,這個鎖與一個特定的物件關聯,執行緒一旦獲取了物件鎖,其他訪問該物件的執行緒就無法再訪問該物件的其他非同步方法
3、對於靜態同步方法,鎖是針對這個類的,鎖物件是該類的Class物件。靜態和非靜態方法的鎖互不干預。一個執行緒獲得鎖,當在一個同步方法中訪問另外物件上的同步方法時,會獲取這兩個物件鎖。
4、對於同步,要時刻清醒在哪個物件上同步,這是關鍵。
5、編寫執行緒安全的類,需要時刻注意對多個執行緒競爭訪問資源的邏輯和安全做出正確的判斷,對“原子”操作做出分析,並保證原子操作期間別的執行緒無法訪問競爭資源。
6、當多個執行緒等待一個物件鎖時,沒有獲取到鎖的執行緒將發生阻塞。
7、死鎖是執行緒間相互等待鎖鎖造成的,在實際中發生的概率非常的小。真讓你寫個死鎖程式,不一定好使,呵呵。但是,一旦程式發生死鎖,程式將死掉。
九、執行緒資料傳遞
在傳統的同步開發模式下,當我們呼叫一個函式時,通過這個函式的引數將資料傳入,並通過這個函式的返回值來返回最終的計算結果。但在多執行緒的非同步開發模式下,資料的傳遞和返回和同步開發模式有很大的區別。由於執行緒的執行和結束是不可預料的,因此,在傳遞和返回資料時就無法象函式一樣通過函式引數和return語句來返回資料。
9.1、通過構造方法傳遞資料
在建立執行緒時,必須要建立一個Thread類的或其子類的例項。因此,我們不難想到在呼叫start方法之前通過執行緒類的構造方法將資料傳入執行緒。並將傳入的資料使用類變數儲存起來,以便執行緒使用(其實就是在run方法中使用)。下面的程式碼演示瞭如何通過構造方法來傳遞資料:
package mythread;
public class MyThread1 extends Thread
{
private String name;
public MyThread1(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
Thread thread = new MyThread1("world");
thread.start();
}
}
由於這種方法是在建立執行緒物件的同時傳遞資料的,因此,線上程執行之前這些資料就就已經到位了,這樣就不會造成資料線上程執行後才傳入的現象。如果要傳遞更復雜的資料,可以使用集合、類等資料結構。使用構造方法來傳遞資料雖然比較安全,但如果要傳遞的資料比較多時,就會造成很多不便。由於Java沒有預設引數,要想實現類似預設引數的效果,就得使用過載,這樣不但使構造方法本身過於複雜,又會使構造方法在數量上大增。因此,要想避免這種情況,就得通過類方法或類變數來傳遞資料。 9.2、通過變數和方法傳遞資料
向物件中傳入資料一般有兩次機會,第一次機會是在建立物件時通過構造方法將資料傳入,另外一次機會就是在類中定義一系列的public的方法或變數(也可稱之為欄位)。然後在建立完物件後,通過物件例項逐個賦值。下面的程式碼是對MyThread1類的改版,使用了一個setName方法來設定 name變數:
package mythread;
public class MyThread2 implements Runnable
{
private String name;
public void setName(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
MyThread2 myThread = new MyThread2();
myThread.setName("world");
Thread thread = new Thread(myThread);
thread.start();
}
}
9.3、通過回撥函式傳遞資料 上面討論的兩種向執行緒中傳遞資料的方法是最常用的。但這兩種方法都是main方法中主動將資料傳入執行緒類的。這對於執行緒來說,是被動接收這些資料的。然而,在有些應用中需要線上程執行的過程中動態地獲取資料,如在下面程式碼的run方法中產生了3個隨機數,然後通過Work類的process方法求這三個隨機數的和,並通過Data類的value將結果返回。從這個例子可以看出,在返回value之前,必須要得到三個隨機數。也就是說,這個 value是無法事先就傳入執行緒類的。
package mythread;
class Data
{
public int value = 0;
}
class Work
{
public void process(Data data, Integer numbers)
{
for (int n : numbers)
{
data.value += n;
}
}
}
public class MyThread3 extends Thread
{
private Work work;
public MyThread3(Work work)
{
this.work = work;
}
public void run()
{
java.util.Random random = new java.util.Random();
Data data = new Data();
int n1 = random.nextInt(1000);
int n2 = random.nextInt(2000);
int n3 = random.nextInt(3000);
work.process(data, n1, n2, n3); // 使用回撥函式
System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"
+ String.valueOf(n3) + "=" + data.value);
}
public static void main(String[] args)
{
Thread thread = new MyThread3(new Work());
thread.start();
}
}
好了,Java多執行緒的基礎知識就講到這裡了,有興趣研究多執行緒的推薦直接看java的原始碼,你將會得到很大的提升!
林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka
相關文章
- java多執行緒(超詳細!)Java執行緒
- Java中的多執行緒程式設計(超詳細總結)Java執行緒程式設計
- Java多執行緒詳解總結Java執行緒
- 最全java多執行緒學習總結1--執行緒基礎Java執行緒
- Java多執行緒超詳解Java執行緒
- java多執行緒總結Java執行緒
- Java多執行緒學習Java執行緒
- Java 多執行緒 - 總結概述Java執行緒
- JAVA 多執行緒總結(一)Java執行緒
- Java多執行緒Queue總結Java執行緒
- Java多執行緒學習(一)Java多執行緒入門Java執行緒
- Java多執行緒學習——執行緒通訊Java執行緒
- Java多執行緒學習(2)執行緒控制Java執行緒
- JAVA學習之路(多執行緒)—模擬售票(細解)Java執行緒
- Java 多執行緒NIO學習Java執行緒
- Java多執行緒(學習篇)Java執行緒
- java多執行緒總結(系列一)Java執行緒
- #大學#Java多執行緒學習02(執行緒同步)Java執行緒
- iOS 多執行緒:『RunLoop』詳盡總結iOS執行緒OOP
- iOS 多執行緒:『GCD』詳盡總結iOS執行緒GC
- iOS多執行緒:『GCD』詳盡總結iOS執行緒GC
- Java 多執行緒學習筆記Java執行緒筆記
- java學習筆記--多執行緒Java筆記執行緒
- java多執行緒學習小案例Java執行緒
- Android的Java多執行緒部簡介和Synchronized學習總結AndroidJava執行緒synchronized
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- 執行緒學習知識總結執行緒
- 最全java多執行緒總結2--如何進行執行緒同步Java執行緒
- Java執行緒總結Java執行緒
- Java多執行緒學習(八)執行緒池與Executor 框架Java執行緒框架
- Java 多執行緒學習筆記(三)-守護執行緒Java執行緒筆記
- Java多執行緒詳解Java執行緒
- iOS 多執行緒總結iOS執行緒
- 這或許是最詳細的JUC多執行緒併發總結執行緒
- 40 個 Java 多執行緒問題總結Java執行緒
- 40個Java多執行緒問題總結Java執行緒
- Java多執行緒學習筆記(自用)Java執行緒筆記
- 多執行緒學習一(多執行緒基礎)執行緒