圖解Java多執行緒設計模式 序章1 Java執行緒
【Thread類的run方法和start方法】
JAVA程式執行時,最開始執行的只能是主執行緒。所以必須在程式中啟動新執行緒。
啟動執行緒時,要使用如下類(一般稱為Thread類)
public class MyThread extends Thread {
public void run(){
for(int i=0;i<100;i++){
System.out.println("Nice!");
}
}
}
新啟動的執行緒的操作都編寫在run方法中。新執行緒啟動後會呼叫run方法。隨後,當run方法執行結束時,執行緒也會跟著終止。
使用者啟動執行緒的程式碼如下
public static void main(String[] args){
MyThread t = new MyThread(); //主執行緒建立MyThread類的例項
t.start(); //由主執行緒啟動新執行緒
for(int i=0;i<100;i++){
System.out.println("Good!");
}
}
start方法是Thread類中的方法,用於啟動新的執行緒。
呼叫start方法後,程式會在後臺啟動新的執行緒。然後,由這個新執行緒呼叫run方法。
【順序、並行與併發】
順序用於表示多個操作 “依次處理”。比如把十個操作交給一個人處理時,這個人要一個一個地按順序來處理。
並行用於表示多個操作“同時處理”。比如十個操作分給兩個人處理時,這兩個人就會並行來處理。
併發相對於順序和並行來說比較抽象,用於表示“將一個操作分割成多個部分並且允許無序處理”。比如將十個操作分成相對獨立的兩類,這樣便能夠哦開始併發處理了。
【執行緒的啟動】
啟動執行緒的方法有如下兩種:
1 利用Thread類的子類的例項啟動執行緒
2 利用Runnable介面的實現類的例項啟動執行緒
執行緒的啟動一:利用Thread類的子類
new MyThread().start();
執行緒的啟動二:利用Runnable介面
Runnable介面包含在java.lang包中,宣告如下
public interface Runnable {
public abstract void run();
}
Runnable介面的實現類必須要實現run方法。
建立Runnable介面的實現類。將實現類的例項作為引數傳給Thread的建構函式,呼叫start方法。
new Thread(new Printer("Good")).start();
(小知識java.util.concurrent.ThreadFactory中的執行緒建立)
ThreadFactory就是一個執行緒工廠也就是負責生產執行緒的。java.util.concurrent中包含一個將執行緒建立抽象化的ThreadFactory介面。利用該介面,我們可以將以Runnable作為傳入引數並通過new建立Thread例項的處理隱藏在ThreadFactory內部。
預設的ThreadFactory物件是通過Executors.defaultThreadFactory方法獲取的。
public static void main(String[] args){
ThreadFactory factory = Executors.defaultThreadFactory();
factory.newThread(new Printer()).start();
for(int i=0;i<100;i++){
System.out.println("hello!");
}
}
【執行緒的暫停】
執行緒Thread類中的sleep方法能夠暫停執行緒執行,sleep是Thread類的靜態方法。
public static void main(String[] args){
for(int i=0;i<100;i++){
System.out.println("hello!");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
}
}
}
【執行緒的互斥處理】
多執行緒程式的各個執行緒都是自由執行的,所以他們有時就會同時操作同一個例項。這在某些情況下會引發問題。
java使用關鍵字synchronized來執行執行緒的互斥處理。
【synchronized方法】
如果宣告一個方法是,在前面加上關鍵字synchronized,那麼這個方法就 只能由一個執行緒執行。稱為同步方法。
public class Bank {
private int money;
private String name;
public Bank(String name, int money){
this.name = name;
this.money = money;
}
//存款
public synchronized void deposit(int m){
money+=m;
}
//取款
public synchronized boolean withdraw(int m){
if(money>=m) {
money-=m;
return true;//取款成功
} else {
return false;//餘額不足
}
}
public String getName(){
return name;
}
}
【執行緒的協作】
如果有一個執行緒正在執行synchronized方法,那麼其他執行緒就無法再執行這個方法了。這是簡單的互斥處理。
假如我們現在想執行更加精確的控制,而不是單純地等待其他執行緒執行終止,例如下面這樣的控制:
如果空間為空則寫入資料;如果非空則一直等待到變空為止
空間已為空時,通知正在等待的執行緒。
Java提供了用於執行執行緒控制的wait方法、notify方法和notifyAll方法。wait是讓執行緒等待的方法,而notify和notifyAll是喚醒等待中的執行緒的方法。
【等待佇列——執行緒休息室】
所有例項都擁有一個等待佇列,它是在例項的wait方法執行後停止操作的執行緒的佇列。
在執行wait方法後,執行緒便會暫停操作,進入等待佇列這個休息室。除非發生下列某一情況,否則執行緒會一直在等待佇列中休眠:
1.有其他執行緒的notify方法喚醒執行緒
2.有其他執行緒的notifyAll方法喚醒執行緒
3.有其他執行緒的interrupt方法喚醒執行緒
4.wait方法超時
若要執行wait方法,執行緒必須持有鎖。但如果執行緒進入等待佇列,便會釋放其例項的鎖。
【習題1 閱讀下面內容,敘述正確請打√,錯誤請打×】
1.在Java程式中,至少有一個執行緒在執行。(√)
2.呼叫Thread類的run方法後,新的執行緒就會啟動。(×)
答:啟動新執行緒的並不是run方法,而是start方法。
3.start方法和run方法宣告在Runnable介面中。(×)
答:只有run方法宣告在Runnable介面中。
4有時多個執行緒會呼叫同一個例項的方法。(√)
答:正因為如此,我們才需要執行執行緒的互斥處理。
5.有時多個執行緒會呼叫Thread類的一個例項的方法。(√)
答:雖說是Thread類的例項方法,但與其他類的例項方法並沒有什麼不同,所以也會被多個執行緒呼叫。
6.sleep方法執行後,在指定的時間所有的執行緒都會暫停。(×)
答:暫停的只是呼叫了sleep方法的執行緒。
7.某個執行緒在執行synchronized方法時,其他所有執行緒都會停止執行。(×)
答:停止執行的只是想要獲取同一個例項的鎖的執行緒。
8.執行sleep方法後的執行緒僅在指定時間內待在等待佇列中。(×)
答:執行sleep方法後的執行緒並不會進入等待佇列。只有在wait方法後,執行緒才會進入等待佇列。
9.wait方法的呼叫語句必須寫在synchronized方法中。(×)
答:synchronized方法或synchronized程式碼塊。只要執行wait方法的執行緒在執行時獲得了物件例項的鎖即可。
10.notifyAll方法是java.lang.Object類的例項方法。(√)
【習題2】
當下面的程式執行時,程式會在輸出1000個“*”後,再輸出1000個“+”。請問,為什麼輸出結果並不是“*”和“+”交錯混雜的呢?
public class main{
public static void main(String[] args){
new PrintThread("*").run();
new PrintThread("+").run();
}
}
public class printThread extends Thread{
private String message;
public printThread(String message){
this.message=message;
}
public void run(){
for(int i=0;i<1000;i++){
System.out.print(message);
}
}
}
答:因為主執行緒呼叫的不是start方法,而是run方法。new PrintThread("*").run();這條語句會建立PrintThread類的例項,並執行該例項的run方法。但執行run方法的並不是新執行緒,而是主執行緒。當輸出100個“*”之後,下面的語句才會被執行。new PrintThread("+").run();
最終,所有的輸出都是由主執行緒這一個執行緒來執行的,也就是說,這個程式其實是一個單執行緒程式。
//建立例項
MyThread t=new MyThread();
//啟動執行緒
t.start();
//暫停已啟動的執行緒
try{
t.sleep(1000);
}catch(InterruptedException e){
}
【習題3】
某人寫了如下程式碼,想讓啟動的執行緒暫停約1秒。但這個程式碼是錯誤的,為什麼呢?假設下面的MyThread就是程式碼清單I1-2中宣告的那個類。
答:這是因為執行t.sleep(1000);時暫停的並不是與t相關的那個執行緒,而是執行這條語句的執行緒。
t.sleep(1000);
上面這條語句調用出來的並不是t的例項方法,而是Thread的靜態方法。也就是說,這等同於執行下面這條語句。
Thread.sleep(1000);
當想要暫停新啟動的執行緒時,我們可以在MyThread類的run方法中呼叫sleep方法。
【習題4】
假設存在一個如下宣告的Something類,變數x,y表示Something類的兩個不同例項。請判斷下列組合是否允許多個執行緒同時執行,允許請畫√,否則請畫×。
public classSomething {
public void iA(){ }
public void iB(){ }
public synchronized void isSyncA(){ }
public synchronized void isSyncB(){ }
public static void cA(){ }
public static void cB(){ }
public static synchronized void cSyncA(){ }
public static synchronized void cSyncB(){ }
}
√(1)x.iA(); 與 x.iA();
答:非synchronized方法可在任意時間多個執行緒執行。
√(2)x.iA(); 與 x.iB();
答:非synchronized方法可在任意時間多個執行緒執行。
√(3)x.iA(); 與 x.iSyncA();
答:非synchronized方法可在任意時間多個執行緒執行,即使存在正在執行其他的synchronized方法的執行緒,非synchronized方法也任然可以由多個執行緒執行。
×(4)x.iSyncA(); 與 x.iSyncA();
答:同一個例項的synchronized例項方法同時只能由一個執行緒執行。
×(5)x.iSyncA(); 與 x.iSyncA();
答:同一個例項的synchronized例項方法同時只能由一個執行緒執行。
√(6)x.iSyncA(); 與 y.iSyncA();
答:例項不同,鎖也就不同,所以就算是synchronized例項方法,也可以由多個執行緒同時執行。
√(7)x.iSyncA(); 與 x.iSyncB();
答:例項不同,鎖也就不同,所以就算是synchronized例項方法,也可以由多個執行緒同時執行。
√(8)x.iSyncA(); 與 Something.cA();
答:靜態方法本來就不是synchronized方法,因此可以同時執行。
√(9)x.iSyncA(); 與 Something.cSyncA();
答:synchronized例項方法和synchronized靜態方法的鎖不同,所以可以由多個執行緒同時執行。
×(10)Something.cSyncA(); 與 Something.cSyncA();
答:synchronized靜態方法不可以有多個執行緒同時執行。
×(11)Something.cSyncA(); 與 Something.cSyncB();
答:synchronized靜態方法不可以有多個執行緒同時執行。
×(12)x.cSyncA(); 與 y.cSyncB();
答:synchronized靜態方法不可以有多個執行緒同時執行。