Java進階05 多執行緒
多執行緒( multiple thread)是計算機實現多工並行處理的一種方式。
在單執行緒情況下,計算機中存在一個控制權,並按照順序依次執行指令。單執行緒好像是一個只有一個隊長指揮的小隊,整個小隊同一個時間只能執行一個任務。
單執行緒
在多執行緒情境下,計算機中有多個控制權。多個控制權可以同時進行,每個控制權依次執行一系列的指令。多執行緒好像是一個小隊中的成員同時執行不同的任務。
可參考 Linux多執行緒與同步,並對比 Python多執行緒與同步
多執行緒
傳統意義上,多執行緒是由作業系統提供的功能。對於單核的CPU,硬體中只存在一個執行緒。在作業系統的控制下,CPU會在不同的任務間(執行緒間)切換,從而造成多工齊頭並進的效果。這是單CPU 分時複用機制下的多執行緒。現在,隨著新的硬體技術的發展,硬體本身開始提供多執行緒支援,比如多核和超執行緒技術。然而,硬體的多執行緒還是要接受作業系統的統一管理。在作業系統之上的多執行緒程式依然通用。
多個執行緒可以並存於同一個程式空間。在JVM的一個程式空間中,一個 棧(stack)代表了方法呼叫的次序。對於多執行緒來說,程式空間中需要有 多個棧,以記錄不同執行緒的呼叫次序。多個棧互不影響,但所有的執行緒將 共享堆(heap)中的物件。
建立執行緒
Java中“一切皆物件”,執行緒也被封裝成一個物件。我們可以透過 繼承Thread類來建立執行緒。執行緒類中的的 run()方法包含了該執行緒應該執行的指令。我們在衍生類中覆蓋該方法,以便向執行緒說明要做的任務:
public class Test { public static void main(String[] args) { NewThread thread1 = new NewThread(); NewThread thread2 = new NewThread(); thread1.start(); // start thread1 thread2.start(); // start thread2 } }/** * create new thread by inheriting Thread */class NewThread extends Thread { private static int threadID = 0; // shared by all /** * constructor */ public NewThread() { super("ID:" + (++threadID)); } /** * convert object to string */ public String toString() { return super.getName(); } /** * what does the thread do? */ public void run() { System.out.println(this); } }
(++是Java中的累加運算子,即讓變數加1。這裡++出現在threadID之前,說明先將threadID加1,再對周邊的表示式求值
toString是Object根類的方法,我們透過覆蓋該方法,來將物件轉換成字串。當我們列印該物件時,Java將自動呼叫該方法。)
可以看到,Thread基類的 構建方法(super())可以接收一個字串作為引數。該字串是該執行緒的名字,並使用 getName()返回。
定義類之後,我們在main()方法中建立執行緒物件。每個執行緒物件為一個執行緒。建立執行緒物件後,執行緒還沒有開始執行。
我們呼叫執行緒物件的 start()方法來啟動執行緒。start()方法可以在構造方法中呼叫。這樣,我們一旦使用 new建立執行緒物件,就立即執行。
Thread類還提供了下面常用方法:
join(Thread tr) 等待執行緒tr完成
setDaemon() 設定當前執行緒為後臺daemon (程式結束不受daemon執行緒的影響)
Thread類官方文件:
Runnable
實現多執行緒的
另一個方式是實施
Runnable介面,並提供run()方法。實施介面的好處是容易實現多重繼承(multiple inheritance)。然而,由於內部類語法,繼承Thread建立執行緒可以實現類似的功能。我們在下面給出一個簡單的例子,而不深入:
public class Test { public static void main(String[] args) { Thread thread1 = new Thread(new NewThread(), "first"); Thread thread2 = new Thread(new NewThread(), "second"); thread1.start(); // start thread1 thread2.start(); // start thread2 } }/** * create new thread by implementing Runnable */class NewThread implements Runnable { /** * convert object to string */ public String toString() { return Thread.currentThread().getName(); } /** * what does the thread do? */ public void run() { System.out.println(this); } }
synchronized
多工程式設計的難點在於多工共享資源。對於同一個程式空間中的多個執行緒來說,它們都共享堆中的物件。某個執行緒對物件的操作,將影響到其它的執行緒。
在多執行緒程式設計中,要盡力避免 競爭條件(racing condition),即執行結果依賴於不同執行緒執行的先後。執行緒是併發執行的,無法確定執行緒的先後,所以我們的程式中不應該出現競爭條件。
然而,當多工共享資源時,就很容易造成競爭條件。我們需要將共享資源,並造成競爭條件的多個執行緒線性化執行,即同一時間只允許一個執行緒執行。
(可更多參考 Linux多執行緒與同步)
下面是一個售票程式。3個售票亭(Booth)共同售賣100張票(Reservoir)。每個售票亭要先判斷是否有餘票,然後再賣出一張票。如果只剩下一張票,在一個售票亭的判斷和售出兩個動作之間,另一個售票亭賣出該票,那麼第一個售票亭(由於已經執行過判斷)依然會齒形賣出,造成票的超賣。為了解決該問題,判斷和售出兩個動作之間不能有“空隙”。也就是說,在一個執行緒完成了這兩個動作之後,才能有另一個執行緒執行。
在Java中,我們將 共享的資源置於一個物件中,比如下面r(Reservoir)物件。它包含了總共的票數;將可能造成競爭條件的,針對共享資源的操作,放在 synchronized (同步)方法中,比如下面的sellTicket()。synchronized是方法的修飾符。在Java中, 同一物件的synchronized方法只能同時被一個執行緒呼叫。其他執行緒必須等待該執行緒呼叫結束,(餘下的執行緒之一)才能執行。這樣,我們就排除了競爭條件的可能。
在main()方法中,我們將共享的資源(r物件)傳遞給多個執行緒:
public
class
Test
{
public
static
void
main(String[] args)
{
Reservoir r =
new Reservoir(100
);
Booth b1 =
new
Booth(r);
Booth b2 =
new
Booth(r);
Booth b3 =
new
Booth(r);
}
}
/**
* contain shared resource
*/
class
Reservoir {
private
int
total;
public Reservoir(
int
t)
{
this.total =
t;
}
/**
* Thread safe method
* serialized access to Booth.total
*/
public
synchronized
boolean
sellTicket()
{
if(
this.total > 0
) {
this.total =
this.total - 1
;
return
true;
//
successfully sell one
}
else
{
return
false;
//
no more tickets
}
}
}
/**
* create new thread by inheriting Thread
*/
class Booth
extends
Thread {
private
static
int threadID = 0;
//
owned by Class object
private Reservoir release;
//
sell this reservoir
private
int count = 0;
//
owned by this thread object
/**
* constructor
*/
public
Booth(Reservoir r) {
super("ID:" + (++
threadID));
this.release = r;
//
all threads share the same reservoir
this
.start();
}
/**
* convert object to string
*/
public
String toString() {
return
super
.getName();
}
/**
* what does the thread do?
*/
public
void
run() {
while(
true
) {
if(
this
.release.sellTicket()) {
this.count =
this.count + 1
;
System.out.println(
this.getName() + ": sell 1"
);
try
{
sleep((
int) Math.random()*100);
//
random intervals
}
catch
(InterruptedException e) {
throw
new
RuntimeException(e);
}
}
else
{
break
;
}
}
System.out.println(
this.getName() + " I sold:" +
count);
}
}
( Math.random()用於產生隨機數)
Java的每個物件都 自動包含有一個用於支援同步的計數器,記錄synchronized方法的呼叫次數。執行緒獲得該計數器,計數器加1,並執行synchronized方法。如果方法內部進一步呼叫了該物件的其他synchronized方法,計數器加1。當synchronized方法呼叫結束並退出時,計數器減1。其他執行緒如果也呼叫了同一物件的synchronized方法,必須等待該計數器變為0,才能鎖定該計數器,開始執行。Java中的類同樣也是物件( Class類物件)。Class類物件也包含有計數器,用於同步。
關鍵程式碼
上面,我們利用synchronized修飾符同步了整個方法。我們可以同步部分程式碼,而不是整個方法。這樣的程式碼被稱為 關鍵程式碼(critical section)。我們使用下面的語法:
synchronized (syncObj) { ...; }
花括號中包含的是 想要同步的程式碼,syncObj是任意物件。我們將使用syncObj物件中的計數器,來同步花括號中的程式碼。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31543790/viewspace-2665088/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 進階Java多執行緒Java執行緒
- Java多執行緒之進階篇Java執行緒
- 05.java多執行緒問題Java執行緒
- Java執行緒池進階Java執行緒
- [shell進階]——shell多執行緒執行緒
- Java進階篇:多執行緒併發實踐Java執行緒
- Java多執行緒程式設計——進階篇一Java執行緒程式設計
- Java多執行緒程式設計——進階篇二Java執行緒程式設計
- java進階(38)--執行緒安全Java執行緒
- python進階(9)多執行緒Python執行緒
- 【重學Java】多執行緒進階(執行緒池、原子性、併發工具類)Java執行緒
- 13Java進階——IO、執行緒Java執行緒
- Java多執行緒——執行緒Java執行緒
- Java 高階 --- 多執行緒快速入門Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——執行緒池Java執行緒
- 多執行緒程式設計進階——Java類庫中的鎖執行緒程式設計Java
- Java進階(六)從ConcurrentHashMap的演進看Java多執行緒核心技術JavaHashMap執行緒
- 【Java多執行緒】輕鬆搞定Java多執行緒(二)Java執行緒
- java——多執行緒Java執行緒
- java 多執行緒Java執行緒
- 【Java】多執行緒Java執行緒
- JAVA 多執行緒 ??Java執行緒
- java多執行緒Java執行緒
- Java - 多執行緒Java執行緒
- java 多執行緒守護執行緒Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- Java多執行緒-執行緒狀態Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- java多執行緒9:執行緒池Java執行緒
- Java多執行緒之執行緒中止Java執行緒
- 【java多執行緒】(二)執行緒停止Java執行緒
- Java多執行緒——守護執行緒Java執行緒
- Java多執行緒16:執行緒組Java執行緒
- Java多執行緒18:執行緒池Java執行緒
- Java多執行緒學習(一)Java多執行緒入門Java執行緒
- Java多執行緒/併發05、synchronized應用例項:執行緒間操作共享資料Java執行緒synchronized
- Java多執行緒(一)多執行緒入門篇Java執行緒