java多線程技術(轉發)

第6 章 Java線程及多線程技術及應用
6.1線程基本概念
1、進程和線程的基礎知識
 進程:運行中的應用程序稱爲進程,擁有系統資源(cpu、內存)
 線程:進程中的一段代碼,一個進程中可以哦有多段代碼。本身不擁有資源(共享所在進程的資源)
在java中,程序入口被自動創建爲主線程,在主線程中可以創建多個子線程。
區別: 1、是否佔有資源問題
       2、創建或撤銷一個進程所需要的開銷比創建或撤銷一個線程所需要的開銷大。
       3、進程爲重量級組件,線程爲輕量級組件

 多進程: 在操作系統中能同時運行多個任務(程序)
 多線程: 在同一應用程序中有多個功能流同時執行

2、線程的主要特點
 不能以一個文件名的方式獨立存在在磁盤中;
 不能單獨執行,只有在進程啓動後纔可啓動;
 線程可以共享進程相同的內存(代碼與數據)。
3、線程的主要用途
 利用它可以完成重複性的工作(如實現動畫、聲音等的播放)。
 從事一次性較費時的初始化工作(如網絡連接、聲音數據文件的加載)。
 併發執行的運行效果(一個進程多個線程)以實現更復雜的功能
4、多線程(多個線程同時運行)程序的主要優點
 可以減輕系統性能方面的瓶頸,因爲可以並行操作;
 提高CPU的處理器的效率,在多線程中,通過優先級管理,可以使重要的程序優先操作,提高了任務管理的靈活性;另一方面,在多CPU系統中,可以把不同的線程在不同的CPU中執行,真正做到同時處理多任務。
6.2 線程創建與啓動
1、與線程編程有關的一些概念
創建方式: 1 繼承java.lang.Thread類    2 實現java.lang.Runnable接口
線程體:public void  run()方法,其內的程序代碼決定了線程的行爲和功能。
線程啓動: public void start () , 線程啓動後,需要獲取cpu才能自動調用run()運行。
線程休眠: public void sleep(long ms), 線程將暫停,放棄cpu

2、利用繼承Thread類創建線程的示例
package com.px1987.j2se.thread.base;
/**通過Thread類實現多線程 定義一個Thread的子類並重寫其run方法.*/
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("invoke MyThread run method");
}
}
public static void main(String[] args) {  // main方法測試線程的創建與啓動
MyThread myThread = new MyThread(); // 實例化MyThread的對象
myThread.start(); // 調用myThread對象的start方法啓動一個線程
}
}
3、利用實現Runable接口創建線程的示例
package com.px1987.j2se.thread.base;
/**通過Runable接口實現多線程 定義MyRunable類實現Runnable接口,並實現接口中的run方法。*/
public class MyRunable implements Runnable {
public void run() {
while (true)
System.out.println("invoke MyRunable run method");
}
public static void main(String[] args) { // main方法測試線程的創建與啓動
// 建立MyRunable類的對象,以此對象爲參數建立Thread類的對象
Thread thread = new Thread(new MyRunable());
thread.start(); // 調用thread對象的start方法啓動一個線程
}
}
6.3 線程的狀態控制
1、新建狀態
用new關鍵字和Thread類或其子類建立一個線程對象後,該線程對象就處於新生狀態。處於新生狀態的線程有自己的內存空間,通過調用start方法進入就緒狀態(runnable)。
2、就緒狀態
處於就緒狀態的線程已經具備了運行條件,但還沒有分配到CPU,處於線程就緒隊列,等待系統爲其分配CPU。等待狀態並不是執行狀態,當系統選定一個等待執行的Thread對象後,它就會從等待執行狀態進入執行狀態,系統挑選的動作稱之爲“cpu調度”。一旦獲得CPU,線程就進入運行狀態並自動調用自己的run方法。
3、死亡狀態
死亡狀態是線程生命週期中的最後一個階段。線程死亡的原因有兩個:
一個是正常運行的線程完成了它的全部工作;
另一個是線程被強制性地終止,如通過執行stop或destroy方法來終止一個線程。
     Method stop() & destroy() in the class Thread is deprecated。
     當一個線程進入死亡狀態以後,就不能再回到其它狀態了。 讓一個Thread對象重新執行一次的唯一方法,就是重新產生一個Thread對象。
4、體現線程狀態轉變的代碼示例
package com.px1987.j2se.thread.base;
public class MyRunable1 implements Runnable {
public void run() {
while (true)
System.out.println("invoke MyRunable run method");
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunable()); // 新生狀態
thread.start(); // 就緒狀態,獲得CPU後就能運行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop(); // 死亡狀態
}
}
通過查API可以看到stop方法和destory方法已經過時了,所以不能再用,那要怎樣做才能強制的銷燬一個線程呢?

1、在run方法中執行return 線程同樣結束
2、可以在while循環的條件中設定一個標誌位,當它等於false的時候,while循環就不在運行,這樣線程也就結束了。代碼爲實現的代碼示例:

package com.px1987.j2se.thread.StateControl;
public class MyRunable2 implements Runnable {   
private boolean isStop; //線程是否停止的標誌位
public void run() {
while (!isStop)
System.out.println("invoke MyRunable run method");
}
public void stop(){ //終止線程
isStop=true;
}
public static void main(String[] args) {
MyRunable myRunable=new MyRunable();
Thread thread = new Thread(myRunable);
thread.start();
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
myRunable.stop(); //正確的停止線程的方法
}
}
5、阻塞狀態
處於運行狀態的線程在某些情況下,如執行了sleep(睡眠)方法,或等待I/O設備等資源,將讓出CPU並暫時停止自己的運行,進入阻塞狀態。
在阻塞狀態的線程不能進入就緒隊列。只有當引起阻塞的原因消除時,如睡眠時間已到,或等待的I/O設備空閒下來,線程便轉入就緒狀態,重新到就緒隊列中排隊等待,被系統選中後從原來停止的位置開始繼續運行。有三種方法可以暫停Threads執行:
(1)sleep方法
可以調用Thread的靜態方法:public static void sleep(long millis) throws InterruptedException 使得當前線程休眠(暫時停止執行millis毫秒)。由於是靜態方法,sleep可以由類名直接調用:Thread.sleep(…)。下面爲代碼示例:
package com.px1987.j2se.thread.p5;
import java.util.Date;
import java.text.SimpleDateFormat;
class SleepTest implements Runnable {
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public void run() {
System.out.println("child thread begin");
int i = 0;
while (i++ < 5) {
System.out.println(format.format(new Date()));
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("child thread dead at: " + format.format(new Date()));
}
public static void main(String[] args) {
Runnable r = new SleepTest();
Thread thread = new Thread(r);
thread.start();
try {
Thread.sleep(20000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("main method dead!");
}
}
該程序的運行結果如下:
child thread begin
2009-02-06 04:50:29
2009-02-06 04:50:34
2009-02-06 04:50:39
2009-02-06 04:50:44
main method dead!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.px1987.j2se.thread.p5.Thread4.run(Thread4.java:17)
at java.lang.Thread.run(Unknown Source)
2009-02-06 04:50:49
child thread dead at: 2009-02-06 04:50:54
(2)yield方法
讓出CPU的使用權,從運行態直接進入就緒態。下面爲代碼示例:
package com.px1987.j2se.thread.StateControl;
class Thread5 implements Runnable {
private String name;
Thread5(String s) {
this.name = s;
}
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println(name + ": " + i);
if (i % 10 == 0) {
Thread.yield();
}
}
}
}
package com.px1987.j2se.thread.StateControl;
public class YieldTest {
public static void main(String[] args) {
Runnable r1 = new Thread5("S1");
Runnable r2 = new Thread5("S2");
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
try {
Thread.sleep(2);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main method over!");
}
}
該程序的部分運行結果如下:
S1: 20
S2: 7
S2: 8
S2: 9
S2: 10
S1: 41
S1: 42
S1: 43
S1: 44
S1: 45
S1: 46
S1: 47
S1: 48
S1: 49
S1: 50
S2: 11
S2: 12
(3)join方法
當某個(A)線程等待另一個線程(B)執行結束後,才繼續執行時,使用join方法。A的 run方法調用b.join()。下面爲代碼示例。
package com.px1987.j2se.thread.join;
class FatherThread implements Runnable {
public void run() {
System.out.println("爸爸想抽菸,發現煙抽完了");
System.out.println("爸爸讓兒子去買包紅塔山");
Thread son = new Thread(new SonThread());
son.start();
System.out.println("爸爸等兒子買菸回來");
try {     //join含義:等待son線程執行完畢,father線程才繼續執行
son.join();
}
catch (InterruptedException e) {
System.out.println("爸爸出門去找兒子跑哪去了");
System.exit(1);
}
System.out.println("爸爸高興的接過煙開始抽,並把零錢給了兒子");
}
}
package com.px1987.j2se.thread.join;
class SonThread implements Runnable {
public void run() {
String tabs="\t\t\t\t\t\t";
System.out.println(tabs+"兒子出門去買菸");
System.out.println(tabs+"兒子買菸需要10分鐘");
try {
for (int i = 0; i < 10;) {
Thread.sleep(1000);
System.out.println(tabs+"兒子出去第" + ++i + "分鐘");
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tabs+"兒子買菸回來了");
}
}
package com.px1987.j2se.thread.join;
public class JoinTest {
public static void main(String[] args) {
System.out.println("爸爸和兒子的故事");
      Thread father = new Thread(new FatherThread());
father.start();
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// father.interrupt();
}
}
該程序的運行結果如下:
爸爸和兒子的故事
爸爸想抽菸,發現煙抽完了
爸爸讓兒子去買包紅塔山
爸爸等兒子買菸回來
兒子出門去買菸
兒子買菸需要10分鐘
兒子出去第1分鐘
兒子出去第2分鐘
兒子出去第3分鐘
兒子出去第4分鐘
兒子出去第5分鐘
兒子出去第6分鐘
兒子出去第7分鐘
兒子出去第8分鐘
兒子出去第9分鐘
兒子出去第10分鐘
兒子買菸回來了
爸爸高興的接過煙開始抽,並把零錢給了兒子
當時間來到兒子出去買菸的時候,Father線程調用interrupt方法就會打斷son線程的正常執行,從而father線程也就不必等待son線程執行完畢再行動了,運行結果如下:
爸爸和兒子的故事
爸爸想抽菸,發現煙抽完了
爸爸讓兒子去買包紅塔山
爸爸等兒子買菸回來
兒子出門去買菸
兒子買菸需要10分鐘
兒子出去第1分鐘
兒子出去第2分鐘
兒子出去第3分鐘
兒子出去第4分鐘
爸爸出門去找兒子跑哪去了
6.4線程的調度和優先級
1、線程的基本信息
方  法 功        能
isAlive() 判斷線程是否還“活”着,即線程是否還未終止。
getPriority() 獲得線程的優先級數值
setPriority() 設置線程的優先級數值
setName() 給線程一個名字
getName() 取得線程的名字
currentThread() 取得當前正在運行的線程對象,也就是取得自己本身
2、操作線程的基本信息代碼示例
package com.px1987.j2se.thread.priority;
public class ThreadInfoTest {
public static void main(String[] argc) throws Exception {
Runnable r = new MyThread();
Thread t = new Thread(r, "Name test");
t.start();
System.out.println("name is: " + t.getName());
Thread.currentThread().sleep(5000);
System.out.println(t.isAlive());
System.out.println("over!");
}
}
class MyThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++)
System.out.println(i);
}
}
該程序的運行結果如下:
name is: Name test
0
1
2
3
. . .
97
98
99
false
over!

3、線程的優先級
(1)優先級(共10級):
它們決定線程執行的先後次序(優先級高者先執行)並可以通過Thread類中的setPriority()和getPriority()方法來改變和獲取優先級。典型的優先級碼
 Thread.MIN_PRIORITY (1級)
 Thread.MAX_PRIORITY(10級)
 Thread.NORM_PRIORITY(5級)
(2)調度規則
Java是不支持線程時間片輪換的調度模型,而採用的是線程優先級高低的搶佔調度模型。具有高優先級的線程可以搶佔低優先級線程運行的機會。高優先級的線程將始終獲得線程執行時間。但是這也不是絕對的,java線程調度器有可能會調用長期處於等待的線程進行執行,所以不要依靠線程的高優先級搶佔模型去完成某些功能。
Java線程調度器支持不同優先級線程的搶先方式,但其本身不支持相同優先級線程的時間片輪換。但是如果java運行時系統所在的操作系統(如windows2000)支持時間片的輪換,則線程調度器就支持相同優先級線程的時間片輪換。
(3)代碼示例
package com.px1987.j2se.thread.priority;
public class ThreadPriorityTest {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread2(), "t1");
Thread t2 = new Thread(new MyThread2(), "t2");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
class MyThread2 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
    System.out.println(Thread.currentThread().getName() + ": " + i);
    yield();
}
}
}
該程序的運行結果如下:

t1: 0
t2: 0
t2: 1
t2: 2
t2: 3
t2: 4
t2: 5
t2: 6
t2: 7
t2: 8
t2: 9
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9
6.5線程同步互斥
1、線程同步互斥的一個示例
多個線程同時訪問或操作同一資源時,很容易出現數據前後不一致的問題。請看下面的例子:

男孩拿着摺子去北京銀行海淀分行取錢
女孩拿着男孩的銀行卡去西單百貨瘋狂購物
男孩走到櫃檯錢詢問帳戶餘額
銀行的業務員小姐親切地告訴他:"您還有10000元!"。
女孩看上了一件時髦的衣裳,準備買下
男孩在思考要取多少錢呢?
女孩到收銀臺準備刷卡消費
收銀臺刷卡機讀取銀行卡餘額爲10000元
女孩買衣服刷卡消費5000元
消費清單打印出來,消費:5000元  餘額:5000元
女孩離開商場
男孩思考了1毫秒
男孩決定取5000元
銀行的業務員小姐爲男孩辦理相關業務手續
交易完成
銀行的業務員小姐告訴男孩:"您的餘額爲5000元"。
男孩離開銀行

男孩帳戶中一共有10000元,男孩拿着存摺從銀行取走5000元,女孩拿着男孩的銀行卡購物刷卡消費5000元,最後男孩的帳戶裏卻還剩5000元。顯然這是不正確的,但是爲什麼會發生這樣的情況呢?我們可以這樣分析:男孩可以看作是一條線程,女孩也可以看作是一條線程,在同一時刻,兩個線程都操作了同一個資源,那就是男孩的帳戶。男孩從查看帳戶餘額到取走現金應該被看作是個原子性操作,是不可再分的,然而當男孩查看完餘額正思考取多少錢的時候,女孩購物消費了5000元,也就是說女孩這條線程打斷了男孩這條線程所要執行的任務。所以男孩剛查看完的餘額10000元就不正確了,最終導致帳戶中少減了5000元。
爲了避免這樣的事情發生,我們要保證線程同步互斥,所謂同步互斥就是:併發執行的多個線程在某一時間內只允許一個線程在執行以訪問共享數據
2、Java中線程互斥的實現機制
由多線程帶來的性能改善是以可靠性爲代價的,所以編程出線程安全的類代碼是十分必要的。當多個線程可以訪問共享資源(調用單個對象的屬性和方法,對數據進行讀、寫、修改、刪除等操作)時,應保證同時只有一個線程訪問共享數據,Java對此提出了有效的解決方案—同步鎖。任何線程要進入同步互斥方法(訪問共享資源的方法或代碼段)時,就必須得到這個共享資源對象的鎖,線程進入同步互斥方法後其它線程則不能再進入同步互斥方法,直到擁有共享資源對象鎖的線程執行完同步互斥方法釋放了鎖,下一個線程才能進入同步互斥方法被執行。
Java的這一線程互斥的實現機制可以用一個最通俗的比方來說明:比如公共衛生間就是一個共享資源,每個人都可以使用,但又不能同時使用,所以衛生間裏有一把鎖。一個人進去了,會把門鎖上,其他人就不能進去。當Ta出來的時候,要打開鎖,下一個人才能繼續使用。
3、利用Synchronized關鍵字用於修飾同步互斥方法
(1)同步互斥方法
public synchronized void method(){
     //允許訪問控制的代碼
}
(2)同步互斥代碼塊
synchronized(syncObject){
//允許訪問控制的代碼
}
(3)鎖定整個類
public synchronized class SyncObject{
}
    由於synchronized 塊可以針對任意的代碼塊,且可任意指定上鎖的對象,因此靈活性較高。但要注意:
 synchronized可以用來限定一個方法或一小段語句或整個類(該類中的所有方法都是synchronized方法)
 將訪問共享數據的代碼設計爲synchronized方法
 由於可以通過 private 關鍵字來保證數據對象只能被方法訪問,所以只需針對方法提出一套同步鎖定機制。通過synchronized 方法來控制對類中的成員變量(共享數據)的訪問。
 編寫線程安全的代碼會使系統的總體效率會降低,要適量使用
 只有某一個線程的synchronized方法執行完後其它線程的synchronized方法才能被執行。
 當前時間,只有一個線程訪問被鎖定的代碼段,但不能保證其他線程去訪問其他沒有被鎖定的代碼段。因此所有對共享資源進行操作的代碼段都應該加鎖。
 對數據庫操作時,修改數據的線程要加鎖,而讀數據的線程可以不加鎖
有了這種解決方案,我們用線程安全的代碼來重新實現一下男孩和女孩取錢的故事。以下是核心代碼:
package com.px1987.j2se.thread.synchronous.v2;
/** 帳戶類 */
public class Account {
/** 餘額 */
private int balance;
public Account(int balance) {
this.balance = balance;
}
}
package com.px1987.j2se.thread.synchronous.v2;
/** 男孩類,實現Runnable接口*/
public class Boy implements Runnable {
/** 銀行帳戶*/
Account account;
public Boy(Account account) {

this.account = account;
}
/** 男孩拿着摺子去北京銀行海淀分行取錢*/
public void run() {
System.out.println("男孩拿着摺子去北京銀行海淀分行取錢");
synchronized (account) {
System.out.println("男孩走到櫃檯錢詢問帳戶餘額");
int balance = account.getBalance();
System.out.println("銀行的業務員小姐親切地告訴他:\"您還有" +
balance + "元!\"。");
try {
System.out.println("男孩在思考要取多少錢呢?");
Thread.sleep(1);
System.out.println("男孩思考了1毫秒");
}
catch (InterruptedException e) {
e.printStackTrace();
}
int money = 5000;
System.out.println("男孩決定取" + money + "元");
System.out.println("銀行的業務員小姐爲男孩辦理相關業務手續");
account.setBalance(balance - money);
System.out.println("交易完成");
System.out.println("銀行的業務員小姐告訴男孩:\"您的餘額爲" +
account.getBalance()+ "元\"。");
}
System.out.println("男孩離開銀行");
}
}
package com.px1987.j2se.thread.synchronous.v2;
/** 女孩類,實現runnable接口*/
public class Girl implements Runnable {
/** 女孩持有男孩的銀行卡*/
Account account;
public Girl(Account account) {

this.account = account;
}
/*** "女孩拿着小軍的銀行卡去西單百貨瘋狂購物*/
public void run() {
String tabs = "\t\t\t\t\t\t";
System.out.println(tabs + "女孩拿着小軍的銀行卡去西單百貨瘋狂購物");
System.out.println(tabs + "女孩看上了一件時髦的衣裳,準備買下");
synchronized (account) {
System.out.println(tabs + "女孩到收銀臺準備刷卡消費");
int balance = account.getBalance();
System.out.println(tabs + "收銀臺刷卡機讀取銀行卡餘額爲" + balance + "元");
int payout = 5000;
System.out.println(tabs + "女孩買衣服刷卡消費" + payout + "元");
account.setBalance(balance - payout);
System.out.println(tabs + "消費清單打印出來,消費:" + payout + "元" + "  餘額:"
+ account.getBalance() + "元");
}
System.out.println(tabs + "女孩離開商場");
}
}
package com.px1987.j2se.thread.synchronous.v2;
public class Bank {
public static void main(String[] args) {
Account account=new Account(10000);
Thread boyThread=new Thread(new Boy(account));
Thread girlThread=new Thread(new Girl(account));
boyThread.start();
girlThread.start();
}
}
修改後的代碼運行結果如下圖:
男孩拿着摺子去北京銀行海淀分行取錢
女孩拿着小軍的銀行卡去西單百貨瘋狂購物
女孩看上了一件時髦的衣裳,準備買下
女孩到收銀臺準備刷卡消費
收銀臺刷卡機讀取銀行卡餘額爲10000元
女孩買衣服刷卡消費5000元
消費清單打印出來,消費:5000元  餘額:5000元
女孩離開商場
男孩走到櫃檯錢詢問帳戶餘額
銀行的業務員小姐親切地告訴他:"您還有5000元!"。
男孩在思考要取多少錢呢?
男孩思考了1毫秒
男孩決定取5000元
銀行的業務員小姐爲男孩辦理相關業務手續
交易完成
銀行的業務員小姐告訴男孩:"您的餘額爲0元"。
男孩離開銀行

從結果中可以看出來,男孩從查看餘額到取錢,女孩沒有操作帳戶,所以最後的餘額是正確的。
4、線程死鎖
使用互斥鎖容易產生死鎖問題。比如:一個線程需要鎖定兩個對象才能完成,線程1擁有對象A的鎖,線程1如果再擁有對象B的鎖就能完成操作,線程2擁有對象B的鎖,線程2如果再擁有對象A的鎖就能完成操作。
很不幸的是線程1執行不下去了,因爲線程1等待的資源對象B被線程2鎖住了,線程2也執行不下去了,因爲線程2等待的資源對象A被線程1鎖住了,這樣就造成了死鎖。

閱讀一段文字:由多線程帶來的性能改善是以可靠性爲代價的,主要是因爲有可能產生線程死鎖。死鎖是這樣一種情形:多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於線程被無限期地阻塞,因此程序不能正常運行。簡單的說就是:線程死鎖時,第一個線程等待第二個線程釋放資源,而同時第二個線程又在等待第一個線程釋放資源。這裏舉一個通俗的例子:如在人行道上兩個人迎面相遇,爲了給對方讓道,兩人同時向一側邁出一步,雙方無法通過,又同時向另一側邁出一步,這樣還是無法通過。假設這種情況一直持續下去,這樣就會發生死鎖現象。
    導致死鎖的根源在於不適當地運用“synchronized”關鍵詞來管理線程對特定對象的訪問。“synchronized”關鍵詞的作用是,確保在某個時刻只有一個線程被允許執行特定的代碼塊,因此,被允許執行的線程首先必須擁有對變量或對象的排他性訪問權。當線程訪問對象時,線程會給對象加鎖,而這個鎖導致其它也想訪問同一對象的線程被阻塞,直至第一個線程釋放它加在對象上的鎖。

(1)死鎖問題的一個代碼示例
package com.px1987.j2se.thread.DeadLock;
class Thread1 implements Runnable {
private Object a;
private Object b;
public Thread1(Object a, Object b) {
super();
this.a = a;
this.b = b;
}
public void run() {
synchronized (a) {
System.out.println("Thread1獲得對象a的鎖");
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("Thread1獲得對象b的鎖");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package com.px1987.j2se.thread.DeadLock;
class Thread2 implements Runnable {
private Object a;
private Object b;
public Thread2(Object a, Object b) {
super();
this.a = a;
this.b = b;
}
public void run() {
synchronized (b) {
System.out.println("Thread2獲得對象b的鎖");
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println("Thread2獲得對象a的鎖");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package com.px1987.j2se.thread.DeadLock;
public class TestDeadLock {
public static void main(String[] args) {
Object a=new Object();
Object b=new Object();
Thread thread1=new Thread(new Thread1(a,b));
Thread thread2=new Thread(new Thread2(a,b));
thread1.start();
thread2.start();

}
}
下面是運行結果:

(2)死鎖問題的另一個代碼示例
package com.px1987.j2se.thread.DeadLock;
public class ThreadDeadLock {
public static void main(String[] args) {
ThreadOne threadOne=new ThreadOne();
ThreadTwo threadTwo=new ThreadTwo();
String s1="s";
String s2="sss";
threadOne.op1=s1;
threadTwo.op1=s1;
threadOne.op2=s2;
threadTwo.op2=s2;
threadOne.start();
threadTwo.start();
}
}
class ThreadOne extends Thread{
String op1;
String op2;
public void run(){// 同步中又有同步,就可能死鎖
synchronized(op1){
System.out.println(Thread.currentThread().getName()+"鎖定op1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(op2){
System.out.println(Thread.currentThread().getName()+"鎖定op2");
}
}
}
}
class ThreadTwo extends Thread{
String op1;
String op2;
public void run(){
synchronized(op2){
System.out.println(Thread.currentThread().getName()+"鎖定op2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(op1){
System.out.println(Thread.currentThread().getName()+"鎖定op1");
}
}
}
}
6.6生產者消費者問題
1、生產者消費者問題的示例
生產者消費者問題也是一個典型的線程問題。我們舉一個這方面的實例來說明:在一個果園裏,有農夫和小孩,農夫會不停的採摘水果放入果園中心的一個水果筐直到水果筐滿,而小孩會不停的從水果筐裏拿水果來喫,直到水果拿完。分析這個模型我們可以看出:農夫可以看成是一個生產者的線程,小孩可以看成是一個消費者的線程,而大水果筐是共享資源。
2、用Java程序表述的代碼示例
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 水果類*/
public class Fruit {
/*** 水果編號*/
private int id;
/*** 水果編號計數器*/
private static int number = 0;
/*** 水果品種 */
private String variety;
/*** 水果品種數組 */
private String[] varietys = "蘋果,桃子,梨子,香蕉,西瓜,荔枝,葡萄".split(",");
public Fruit() {
super();
this.variety = varietys[new Random().nextInt(7)];
this.id = ++number;
}
}

水果筐應該設計成類似於棧的數據結構,其中包含一個數組來存放筐裏的水果,而數組的下標就是水果筐的容量。設定一個索引index表示指向下一個將要放入水果的位置。類中的push方法模擬農夫向水果筐中放入水果,pop方法模擬小孩從水果筐中拿水果。這兩個方法都要操作共享資源,所以push和pop方法都是同步互斥方法。
3、如何避免出現死鎖
那同步的問題解決後是否會出現死鎖呢?大家試想一下,如果生產的速度大於消費的速度就會導致功大於求,水果筐很容易就滿了,然而生產者又一直抱着水果筐不放,沒有機會給消費者使用,消費者不消費生產者就無法生產,所以就造成了死鎖。
怎樣解決呢?在兩個同步互斥方法中用到了wait和notify方法,這兩個方法是爲了防止死鎖的。
 wait是Object類的方法,它的作用是擁有互斥鎖的線程放棄鎖的使用權,進入wait池進行等待,那麼互斥鎖就有可能被其他線程獲得以執行其他任務。
 notify也是Object類的方法,它的作用是從wait池中喚醒一條正在等待的線程進入就緒狀態,被喚醒的這條線程就很可能重新獲得cup和互斥鎖來完成它的任務。
 notifyAll和Notify很相似,它是從wait池中喚醒所有正在等待的線程進入就緒狀態。
需要注意的是以上三個方法都只能在synchronized方法中應用,否者會出現下面的異常信息:IllegalMonitorStateException:current thread not owner。
4、實現的代碼示例
package com.px1987.j2se.thread.ProducerConsumer;
import java.text.DecimalFormat;
import java.util.Arrays;
/*** 水果框類,類似一個棧的模型 */
public class FruitBasket {
/*** 容量爲10的水果數組,也就是說水果框最多能放下10個水果 */
private Fruit[] fruits = new Fruit[10];
/*** 下一個將要放入水果的位置*/
private int index = 0;
/*** 水果框中是否爲空 @return true爲空,false爲不空 */
public boolean isEmpty() {
return index == 0 ? true : false;
}
/*** 水果框是否裝滿* @return true爲滿,false爲未滿*/
public boolean isFull() {
return index == fruits.length ? true : false;
}
/*** 進棧方法,模擬農夫把水果放入筐中,@param name 農夫的名字,@param fruit 水果對象 */
public synchronized void push(String name, Fruit fruit) {
//用while循環,不用if,避免IndexOutOfBoundsException異常的產生
while (isFull()) {
//如果水果筐滿了,需要等待
try {
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
//將水果放入index指示的位置,index再上移一格
fruits[index++] = fruit;
System.out.println(name + " 向水果框中放入編號爲" + fruit.getId() + "的"+
fruit.getVariety());
display();
this.notify(); //通知其他等待的農夫或孩子可以開始工作啦
}
/*** 出棧方法,模擬小孩從水果筐中取出水果,@param name 小孩的名字,@return 取出的水果*/
public synchronized Fruit pop(String name) {
//用while循環,不用if,避免IndexOutOfBoundsException異常的產生
while (isEmpty()) {
try { //如果水果筐空,需要等待
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
Fruit fruit = null;
fruit = fruits[--index]; //index下移一位,取出指示位置上的水果
System.out.println(name + " 從水果框中拿出編號爲" + fruit.getId() + "的"+
fruit.getVariety());
display();
this.notify();
return fruit;
}
/*** 顯示水果筐中水果存放情況*/
public void display() {
for (int i = 0; i < index; i++)
System.out.printf("%-10s", " NO:" +
new DecimalFormat("00").format(fruits[i].getId())
+ fruits[i].getVariety() + " |");
for (int i = index; i < fruits.length; i++) {
System.out.printf("%-10s", "   【" + (i + 1) + "】   |");
}
System.out.println();
}
}
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/** 果園裏的農夫類,他是生產者,實現Runnable接口*/
public class Farmer implements Runnable {
/** 姓名*/
private String name;
/** 水果框*/
private FruitBasket fruitBasket;
/** 農夫會不停地重複這一系列動作:從水果樹上採摘一個水果放入水果框中,然後隨機的休息0-2秒*/
public void run() {
while (true) {
fruitBasket.push(name, new Fruit());
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Farmer(String name, FruitBasket fruitBasket) {
super();
this.name = name;
this.fruitBasket = fruitBasket;
}
}
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 果園中的小孩類,他是消費者,實現Runnable接口*/
public class Child implements Runnable {
/*** 姓名*/
private String name;
/*** 水果框*/
private FruitBasket fruitBasket;
/*** 小孩會不停地重複這一系列動作:從水果框中拿出水果喫,然後隨機休息0-5秒鐘*/
public void run() {
while (true) {
fruitBasket.pop(name);
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Child(String name, FruitBasket fruitBasket) {
super();
this.name = name;
this.fruitBasket = fruitBasket;
}
}
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 果園中的小孩類,他是消費者,實現Runnable接口*/
public class Child implements Runnable {
/*** 姓名*/
private String name;
/*** 水果框*/
private FruitBasket fruitBasket;
/*** 小孩會不停地重複這一系列動作:從水果框中拿出水果喫,然後隨機休息0-5秒鐘*/
public void run() {
while (true) {
fruitBasket.pop(name);
try {
Thread.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Child(String name, FruitBasket fruitBasket) {
super();
this.name = name;
this.fruitBasket = fruitBasket;
}
}
測試時使用多個生產者線程和多個消費者線程
package com.px1987.j2se.thread.ProducerConsumer;
/** 果園類,測試*/
public class Orchard {
public static void main(String[] args) {
FruitBasket fruitBasket = new FruitBasket();
Thread farmerThread1 = new Thread(new Farmer("農夫1", fruitBasket));
Thread farmerThread2 = new Thread(new Farmer("農夫2", fruitBasket));
Thread farmerThread3 = new Thread(new Farmer("農夫3", fruitBasket));
Thread childThread1 = new Thread(new Child("小孩1", fruitBasket));
Thread childThread2 = new Thread(new Child("小孩2", fruitBasket));
Thread childThread3 = new Thread(new Child("小孩3", fruitBasket));
farmerThread1.start();
farmerThread2.start();
farmerThread3.start();
childThread1.start();
childThread2.start();
childThread3.start();
}
}
程序的運行結果如下:

農夫1 向水果框中放入編號爲1的蘋果
NO:01蘋果 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫3 向水果框中放入編號爲2的荔枝
NO:01蘋果 | NO:02荔枝 |   【3】   |   【4】   |   【5】   |   【6】   |
小孩2 從水果框中拿出編號爲2的荔枝
NO:01蘋果 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫2 向水果框中放入編號爲3的香蕉
NO:01蘋果 | NO:03香蕉 |   【3】   |   【4】   |   【5】   |   【6】   |
小孩1 從水果框中拿出編號爲3的香蕉
NO:01蘋果 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
小孩3 從水果框中拿出編號爲1的蘋果
   【1】   |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫2 向水果框中放入編號爲4的蘋果
NO:04蘋果 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
小孩1 從水果框中拿出編號爲4的蘋果
   【1】   |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫1 向水果框中放入編號爲5的蘋果
NO:05蘋果 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫3 向水果框中放入編號爲6的西瓜
NO:05蘋果 | NO:06西瓜 |   【3】   |   【4】   |   【5】   |   【6】   |
農夫2 向水果框中放入編號爲7的蘋果
NO:05蘋果 | NO:06西瓜 | NO:07蘋果 |   【4】   |   【5】   |   【6】   |
小孩3 從水果框中拿出編號爲7的蘋果
NO:05蘋果 | NO:06西瓜 |   【3】   |   【4】   |   【5】   |   【6】   |
小孩3 從水果框中拿出編號爲6的西瓜
NO:05蘋果 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
小孩2 從水果框中拿出編號爲5的蘋果
   【1】   |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫2 向水果框中放入編號爲8的桃子
NO:08桃子 |   【2】   |   【3】   |   【4】   |   【5】   |   【6】   |
農夫1 向水果框中放入編號爲9的荔枝
NO:08桃子 | NO:09荔枝 |   【3】   |   【4】   |   【5】   |   【6】   |
農夫3 向水果框中放入編號爲10的香蕉
NO:08桃子 | NO:09荔枝 | NO:10香蕉 |   【4】   |   【5】   |   【6】   |
農夫1 向水果框中放入編號爲11的桃子
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 |   【5】   |   【6】   |
農夫1 向水果框中放入編號爲12的荔枝
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 |   【6】   |
農夫3 向水果框中放入編號爲13的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:13西瓜 |
小孩1 從水果框中拿出編號爲13的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 |   【6】   |
農夫2 向水果框中放入編號爲14的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:14西瓜 |
6.7 課後複習題
1、線程的應用示例——繼承Thread類
import java.util.*;
public class ThreadExampleOne{
public static void main(String args[]){
TimeThread timeThread = new TimeThread();
timeThread.start();
}
}
class TimeThread extends Thread{
Date nowTime;
public void run(){  //重寫run方法
while(true){
nowTime=new Date();
System.out.println("現在的時間爲:"+nowTime.getHours()+
  ":"+nowTime.getMinutes()+":"+nowTime.getSeconds());
try{
sleep(1000);
}
catch(InterruptedException e){
System.out.println(e.toString());
}
}
}
}
2、線程的應用示例——實現Runnable接口
import java.util.*;
public class ThreadExampleTwo{
public static void main(String args[]){
TimeThread timeThreadObj = new TimeThread();
}
}
class TimeThread extends Object implements Runnable{
Date nowTime;
Thread timeThread=null;
public TimeThread(){
timeThread=new Thread(this);
timeThread.start();
}
public void run(){  //重寫run方法
while(true){
nowTime=new Date();
System.out.println("現在的時間爲:"+nowTime.getHours()+
    ":"+nowTime.getMinutes()+":"+nowTime.getSeconds());
try{
timeThread.sleep(1000);
}
catch(InterruptedException e){
System.out.println(e.toString());
}
}
}
}
3、線程優先級的應用示例
class ThreadPriorityDemo extends Thread{
ThreadPriorityDemo(String strName){
System.out.println("線程名稱:"+strName);
}
public void run(){
//打印出當前線程的優先級
System.out.println(this.getPriority());
}
}
class PriorityDemo{
public static void main(String args[]){
ThreadPriorityDemo aThreadDemo = new ThreadPriorityDemo("Thread A");
aThreadDemo.setPriority(Thread.MAX_PRIORITY);
aThreadDemo.start();
ThreadPriorityDemo bThreadDemo = new ThreadPriorityDemo("Thread B");
bThreadDemo.setPriority(Thread.MIN_PRIORITY);
bThreadDemo.start();
ThreadPriorityDemo cThreadDemo = new ThreadPriorityDemo("Thread C");
cThreadDemo.setPriority(Thread.NORM_PRIORITY);
cThreadDemo.start();
ThreadPriorityDemo dThreadDemo = new ThreadPriorityDemo("Thread D");
dThreadDemo.setPriority(Thread.MIN_PRIORITY);
dThreadDemo.start();
ThreadPriorityDemo eThreadDemo = new ThreadPriorityDemo("Thread E");
eThreadDemo.setPriority(Thread.MIN_PRIORITY);
eThreadDemo.start();
}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章