第6 章 Java線(xiàn)程及多線(xiàn)程技術(shù)及應(yīng)用
6.1線(xiàn)程基本概念
1、進(jìn)程和線(xiàn)程的基礎(chǔ)知識(shí)
l 進(jìn)程:運(yùn)行中的應(yīng)用程序稱(chēng)為進(jìn)程,擁有系統(tǒng)資源(cpu、內(nèi)存)
l 線(xiàn)程:進(jìn)程中的一段代碼,一個(gè)進(jìn)程中可以哦有多段代碼,
Java線(xiàn)程及多線(xiàn)程技術(shù)及應(yīng)用
。本身不擁有資源(共享所在進(jìn)程的資源)在java中,程序入口被自動(dòng)創(chuàng)建為主線(xiàn)程,在主線(xiàn)程中可以創(chuàng)建多個(gè)子線(xiàn)程。
區(qū)別: 1、是否占有資源問(wèn)題
2、創(chuàng)建或撤銷(xiāo)一個(gè)進(jìn)程所需要的開(kāi)銷(xiāo)比創(chuàng)建或撤銷(xiāo)一個(gè)線(xiàn)程所需要的開(kāi)銷(xiāo)大。
3、進(jìn)程為重量級(jí)組件,線(xiàn)程為輕量級(jí)組件
l 多進(jìn)程: 在操作系統(tǒng)中能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)
l 多線(xiàn)程: 在同一應(yīng)用程序中有多個(gè)功能流同時(shí)執(zhí)行
2、線(xiàn)程的主要特點(diǎn)
l 不能以一個(gè)文件名的方式獨(dú)立存在在磁盤(pán)中;
l 不能單獨(dú)執(zhí)行,只有在進(jìn)程啟動(dòng)后才可啟動(dòng);
l 線(xiàn)程可以共享進(jìn)程相同的內(nèi)存(代碼與數(shù)據(jù))。
3、線(xiàn)程的主要用途
l 利用它可以完成重復(fù)性的工作(如實(shí)現(xiàn)動(dòng)畫(huà)、聲音等的播放)。
l 從事一次性較費(fèi)時(shí)的初始化工作(如網(wǎng)絡(luò)連接、聲音數(shù)據(jù)文件的加載)。
l 并發(fā)執(zhí)行的運(yùn)行效果(一個(gè)進(jìn)程多個(gè)線(xiàn)程)以實(shí)現(xiàn)更復(fù)雜的功能
4、多線(xiàn)程(多個(gè)線(xiàn)程同時(shí)運(yùn)行)程序的主要優(yōu)點(diǎn)
l 可以減輕系統(tǒng)性能方面的瓶頸,因?yàn)榭梢圆⑿胁僮鳎?/p>
l 提高CPU的處理器的效率,在多線(xiàn)程中,通過(guò)優(yōu)先級(jí)管理,可以使重要的程序優(yōu)先操作,提高了任務(wù)管理的靈活性;另一方面,在多CPU系統(tǒng)中,可以把不同的線(xiàn)程在不同的CPU中執(zhí)行,真正做到同時(shí)處理多任務(wù)。
6.2 線(xiàn)程創(chuàng)建與啟動(dòng)
1、與線(xiàn)程編程有關(guān)的一些概念
創(chuàng)建方式: 1 繼承java.lang.Thread類(lèi) 2 實(shí)現(xiàn)java.lang.Runnable接口
線(xiàn)程體:public void run()方法,其內(nèi)的程序代碼決定了線(xiàn)程的行為和功能。
線(xiàn)程啟動(dòng): public void start () , 線(xiàn)程啟動(dòng)后,需要獲取cpu才能自動(dòng)調(diào)用run()運(yùn)行。
線(xiàn)程休眠: public void sleep(long ms), 線(xiàn)程將暫停,放棄cpu
2、利用繼承Thread類(lèi)創(chuàng)建線(xiàn)程的示例
package com.px1987.j2se.thread.base;
/**通過(guò)Thread類(lèi)實(shí)現(xiàn)多線(xiàn)程 定義一個(gè)Thread的子類(lèi)并重寫(xiě)其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方法測(cè)試線(xiàn)程的創(chuàng)建與啟動(dòng)
MyThread myThread = new MyThread(); // 實(shí)例化MyThread的對(duì)象
myThread.start(); // 調(diào)用myThread對(duì)象的start方法啟動(dòng)一個(gè)線(xiàn)程
}
}
3、利用實(shí)現(xiàn)Runable接口創(chuàng)建線(xiàn)程的示例
package com.px1987.j2se.thread.base;
/**通過(guò)Runable接口實(shí)現(xiàn)多線(xiàn)程 定義MyRunable類(lèi)實(shí)現(xiàn)Runnable接口,并實(shí)現(xiàn)接口中的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方法測(cè)試線(xiàn)程的創(chuàng)建與啟動(dòng)
// 建立MyRunable類(lèi)的對(duì)象,以此對(duì)象為參數(shù)建立Thread類(lèi)的對(duì)象
Thread thread = new Thread(new MyRunable());
thread.start(); // 調(diào)用thread對(duì)象的start方法啟動(dòng)一個(gè)線(xiàn)程
}
}
6.3 線(xiàn)程的狀態(tài)控制
1、新建狀態(tài)
用new關(guān)鍵字和Thread類(lèi)或其子類(lèi)建立一個(gè)線(xiàn)程對(duì)象后,該線(xiàn)程對(duì)象就處于新生狀態(tài)。處于新生狀態(tài)的線(xiàn)程有自己的內(nèi)存空間,通過(guò)調(diào)用start方法進(jìn)入就緒狀態(tài)(runnable)。
2、就緒狀態(tài)
處于就緒狀態(tài)的線(xiàn)程已經(jīng)具備了運(yùn)行條件,但還沒(méi)有分配到CPU,處于線(xiàn)程就緒隊(duì)列,等待系統(tǒng)為其分配CPU。等待狀態(tài)并不是執(zhí)行狀態(tài),當(dāng)系統(tǒng)選定一個(gè)等待執(zhí)行的Thread對(duì)象后,它就會(huì)從等待執(zhí)行狀態(tài)進(jìn)入執(zhí)行狀態(tài),系統(tǒng)挑選的動(dòng)作稱(chēng)之為“cpu調(diào)度”。一旦獲得CPU,線(xiàn)程就進(jìn)入運(yùn)行狀態(tài)并自動(dòng)調(diào)用自己的run方法。
3、死亡狀態(tài)
死亡狀態(tài)是線(xiàn)程生命周期中的最后一個(gè)階段。線(xiàn)程死亡的原因有兩個(gè):
一個(gè)是正常運(yùn)行的線(xiàn)程完成了它的全部工作;
另一個(gè)是線(xiàn)程被強(qiáng)制性地終止,如通過(guò)執(zhí)行stop或destroy方法來(lái)終止一個(gè)線(xiàn)程。
Method stop() & destroy() in the class Thread is deprecated。
當(dāng)一個(gè)線(xiàn)程進(jìn)入死亡狀態(tài)以后,就不能再回到其它狀態(tài)了。 讓一個(gè)Thread對(duì)象重新執(zhí)行一次的唯一方法,就是重新產(chǎn)生一個(gè)Thread對(duì)象。
4、體現(xiàn)線(xiàn)程狀態(tài)轉(zhuǎn)變的代碼示例
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()); // 新生狀態(tài)
thread.start(); // 就緒狀態(tài),獲得CPU后就能運(yùn)行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop(); // 死亡狀態(tài)
}
}
通過(guò)查API可以看到stop方法和destory方法已經(jīng)過(guò)時(shí)了,所以不能再用,那要怎樣做才能強(qiáng)制的銷(xiāo)毀一個(gè)線(xiàn)程呢?
1、在run方法中執(zhí)行return 線(xiàn)程同樣結(jié)束
2、可以在while循環(huán)的條件中設(shè)定一個(gè)標(biāo)志位,當(dāng)它等于false的時(shí)候,while循環(huán)就不在運(yùn)行,這樣線(xiàn)程也就結(jié)束了。代碼為實(shí)現(xiàn)的代碼示例:
package com.px1987.j2se.thread.StateControl;
public class MyRunable2 implements Runnable {
private boolean isStop; //線(xiàn)程是否停止的標(biāo)志位
public void run() {
while (!isStop)
System.out.println(invoke MyRunable run method);
}
public void stop(){ //終止線(xiàn)程
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(); //正確的停止線(xiàn)程的方法
}
}
5、阻塞狀態(tài)
處于運(yùn)行狀態(tài)的線(xiàn)程在某些情況下,如執(zhí)行了sleep(睡眠)方法,或等待I/O設(shè)備等資源,將讓出CPU并暫時(shí)停止自己的運(yùn)行,進(jìn)入阻塞狀態(tài)。
在阻塞狀態(tài)的線(xiàn)程不能進(jìn)入就緒隊(duì)列。只有當(dāng)引起阻塞的原因消除時(shí),如睡眠時(shí)間已到,或等待的I/O設(shè)備空閑下來(lái),線(xiàn)程便轉(zhuǎn)入就緒狀態(tài),重新到就緒隊(duì)列中排隊(duì)等待,被系統(tǒng)選中后從原來(lái)停止的位置開(kāi)始繼續(xù)運(yùn)行。有三種方法可以暫停Threads執(zhí)行:
(1)sleep方法
可以調(diào)用Thread的靜態(tài)方法:public static void sleep(long millis) throws InterruptedException 使得當(dāng)前線(xiàn)程休眠(暫時(shí)停止執(zhí)行millis毫秒)。由于是靜態(tài)方法,sleep可以由類(lèi)名直接調(diào)用: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!);
}
}
該程序的運(yùn)行結(jié)果如下:
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的使用權(quán),從運(yùn)行態(tài)直接進(jìn)入就緒態(tài)。下面為代碼示例:
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!);
}
}
該程序的部分運(yùn)行結(jié)果如下:
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方法
當(dāng)某個(gè)(A)線(xiàn)程等待另一個(gè)線(xiàn)程(B)執(zhí)行結(jié)束后,才繼續(xù)執(zhí)行時(shí),使用join方法。A的 run方法調(diào)用b.join()。下面為代碼示例。
package com.px1987.j2se.thread.join;
class FatherThread implements Runnable {
public void run() {
System.out.println(爸爸想抽煙,發(fā)現(xiàn)煙抽完了);
System.out.println(爸爸讓兒子去買(mǎi)包紅塔山);
Thread son = new Thread(new SonThread());
son.start();
System.out.println(爸爸等兒子買(mǎi)煙回來(lái));
try { //join含義:等待son線(xiàn)程執(zhí)行完畢,father線(xiàn)程才繼續(xù)執(zhí)行
son.join();
}
catch (InterruptedException e) {
System.out.println(爸爸出門(mén)去找兒子跑哪去了);
System.exit(1);
}
System.out.println(爸爸高興的接過(guò)煙開(kāi)始抽,并把零錢(qián)給了兒子);
}
}
package com.px1987.j2se.thread.join;
class SonThread implements Runnable {
public void run() {
String tabs= ;
System.out.println(tabs+兒子出門(mén)去買(mǎi)煙);
System.out.println(tabs+兒子買(mǎi)煙需要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+兒子買(mǎi)煙回來(lái)了);
}
}
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();
}
}
該程序的運(yùn)行結(jié)果如下:
爸爸和兒子的故事
爸爸想抽煙,發(fā)現(xiàn)煙抽完了
爸爸讓兒子去買(mǎi)包紅塔山
爸爸等兒子買(mǎi)煙回來(lái)
兒子出門(mén)去買(mǎi)煙
兒子買(mǎi)煙需要10分鐘
兒子出去第1分鐘
兒子出去第2分鐘
兒子出去第3分鐘
兒子出去第4分鐘
兒子出去第5分鐘
兒子出去第6分鐘
兒子出去第7分鐘
兒子出去第8分鐘
兒子出去第9分鐘
兒子出去第10分鐘
兒子買(mǎi)煙回來(lái)了
爸爸高興的接過(guò)煙開(kāi)始抽,并把零錢(qián)給了兒子
當(dāng)時(shí)間來(lái)到兒子出去買(mǎi)煙的時(shí)候,F(xiàn)ather線(xiàn)程調(diào)用interrupt方法就會(huì)打斷son線(xiàn)程的正常執(zhí)行,從而father線(xiàn)程也就不必等待son線(xiàn)程執(zhí)行完畢再行動(dòng)了,運(yùn)行結(jié)果如下:
爸爸和兒子的故事
爸爸想抽煙,發(fā)現(xiàn)煙抽完了
爸爸讓兒子去買(mǎi)包紅塔山
爸爸等兒子買(mǎi)煙回來(lái)
兒子出門(mén)去買(mǎi)煙
兒子買(mǎi)煙需要10分鐘
兒子出去第1分鐘
兒子出去第2分鐘
兒子出去第3分鐘
兒子出去第4分鐘
爸爸出門(mén)去找兒子跑哪去了
6.4線(xiàn)程的調(diào)度和優(yōu)先級(jí)
1、線(xiàn)程的基本信息
方 法
功 能
isAlive()
判斷線(xiàn)程是否還“活”著,即線(xiàn)程是否還未終止。
getPriority()
獲得線(xiàn)程的優(yōu)先級(jí)數(shù)值
setPriority()
設(shè)置線(xiàn)程的優(yōu)先級(jí)數(shù)值
setName()
給線(xiàn)程一個(gè)名字
getName()
取得線(xiàn)程的名字
currentThread()
取得當(dāng)前正在運(yùn)行的線(xiàn)程對(duì)象,也就是取得自己本身
2、操作線(xiàn)程的基本信息代碼示例
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);
}
}
該程序的運(yùn)行結(jié)果如下:
name is: Name test
0
1
2
3
. . .
97
98
99
false
over!
3、線(xiàn)程的優(yōu)先級(jí)
(1)優(yōu)先級(jí)(共10級(jí)):
它們決定線(xiàn)程執(zhí)行的先后次序(優(yōu)先級(jí)高者先執(zhí)行)并可以通過(guò)Thread類(lèi)中的setPriority()和getPriority()方法來(lái)改變和獲取優(yōu)先級(jí)。典型的優(yōu)先級(jí)碼
n Thread.MIN_PRIORITY (1級(jí))
n Thread.MAX_PRIORITY(10級(jí))
n Thread.NORM_PRIORITY(5級(jí))
(2)調(diào)度規(guī)則
Java是不支持線(xiàn)程時(shí)間片輪換的調(diào)度模型,而采用的是線(xiàn)程優(yōu)先級(jí)高低的搶占調(diào)度模型。具有高優(yōu)先級(jí)的線(xiàn)程可以搶占低優(yōu)先級(jí)線(xiàn)程運(yùn)行的機(jī)會(huì)。高優(yōu)先級(jí)的線(xiàn)程將始終獲得線(xiàn)程執(zhí)行時(shí)間。但是這也不是絕對(duì)的,java線(xiàn)程調(diào)度器有可能會(huì)調(diào)用長(zhǎng)期處于等待的線(xiàn)程進(jìn)行執(zhí)行,所以不要依靠線(xiàn)程的高優(yōu)先級(jí)搶占模型去完成某些功能。
Java線(xiàn)程調(diào)度器支持不同優(yōu)先級(jí)線(xiàn)程的搶先方式,但其本身不支持相同優(yōu)先級(jí)線(xiàn)程的時(shí)間片輪換。但是如果java運(yùn)行時(shí)系統(tǒng)所在的操作系統(tǒng)(如windows2000)支持時(shí)間片的輪換,則線(xiàn)程調(diào)度器就支持相同優(yōu)先級(jí)線(xiàn)程的時(shí)間片輪換。
(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();
}
}
}
該程序的運(yùn)行結(jié)果如下:
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線(xiàn)程同步互斥
1、線(xiàn)程同步互斥的一個(gè)示例
多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)或操作同一資源時(shí),很容易出現(xiàn)數(shù)據(jù)前后不一致的問(wèn)題。請(qǐng)看下面的例子:
男孩拿著折子去北京銀行海淀分行取錢(qián)
女孩拿著男孩的銀行卡去西單百貨瘋狂購(gòu)物
男孩走到柜臺(tái)錢(qián)詢(xún)問(wèn)帳戶(hù)余額
銀行的業(yè)務(wù)員小姐親切地告訴他:您還有10000元!。
女孩看上了一件時(shí)髦的衣裳,準(zhǔn)備買(mǎi)下
男孩在思考要取多少錢(qián)呢?
女孩到收銀臺(tái)準(zhǔn)備刷卡消費(fèi)
收銀臺(tái)刷卡機(jī)讀取銀行卡余額為10000元
女孩買(mǎi)衣服刷卡消費(fèi)5000元
消費(fèi)清單打印出來(lái),消費(fèi):5000元 余額:5000元
女孩離開(kāi)商場(chǎng)
男孩思考了1毫秒
男孩決定取5000元
銀行的業(yè)務(wù)員小姐為男孩辦理相關(guān)業(yè)務(wù)手續(xù)
交易完成
銀行的業(yè)務(wù)員小姐告訴男孩:您的余額為5000元。
男孩離開(kāi)銀行
男孩帳戶(hù)中一共有10000元,男孩拿著存折從銀行取走5000元,女孩拿著男孩的銀行卡購(gòu)物刷卡消費(fèi)5000元,最后男孩的帳戶(hù)里卻還剩5000元。顯然這是不正確的,但是為什么會(huì)發(fā)生這樣的情況呢?我們可以這樣分析:男孩可以看作是一條線(xiàn)程,女孩也可以看作是一條線(xiàn)程,在同一時(shí)刻,兩個(gè)線(xiàn)程都操作了同一個(gè)資源,那就是男孩的帳戶(hù)。男孩從查看帳戶(hù)余額到取走現(xiàn)金應(yīng)該被看作是個(gè)原子性操作,是不可再分的,然而當(dāng)男孩查看完余額正思考取多少錢(qián)的時(shí)候,女孩購(gòu)物消費(fèi)了5000元,也就是說(shuō)女孩這條線(xiàn)程打斷了男孩這條線(xiàn)程所要執(zhí)行的任務(wù)。所以男孩剛查看完的余額10000元就不正確了,最終導(dǎo)致帳戶(hù)中少減了5000元。
為了避免這樣的事情發(fā)生,我們要保證線(xiàn)程同步互斥,所謂同步互斥就是:并發(fā)執(zhí)行的多個(gè)線(xiàn)程在某一時(shí)間內(nèi)只允許一個(gè)線(xiàn)程在執(zhí)行以訪(fǎng)問(wèn)共享數(shù)據(jù)
2、Java中線(xiàn)程互斥的實(shí)現(xiàn)機(jī)制
由多線(xiàn)程帶來(lái)的性能改善是以可靠性為代價(jià)的,所以編程出線(xiàn)程安全的類(lèi)代碼是十分必要的。當(dāng)多個(gè)線(xiàn)程可以訪(fǎng)問(wèn)共享資源(調(diào)用單個(gè)對(duì)象的屬性和方法,對(duì)數(shù)據(jù)進(jìn)行讀、寫(xiě)、修改、刪除等操作)時(shí),應(yīng)保證同時(shí)只有一個(gè)線(xiàn)程訪(fǎng)問(wèn)共享數(shù)據(jù),Java對(duì)此提出了有效的解決方案—同步鎖。任何線(xiàn)程要進(jìn)入同步互斥方法(訪(fǎng)問(wèn)共享資源的方法或代碼段)時(shí),就必須得到這個(gè)共享資源對(duì)象的鎖,線(xiàn)程進(jìn)入同步互斥方法后其它線(xiàn)程則不能再進(jìn)入同步互斥方法,直到擁有共享資源對(duì)象鎖的線(xiàn)程執(zhí)行完同步互斥方法釋放了鎖,下一個(gè)線(xiàn)程才能進(jìn)入同步互斥方法被執(zhí)行。
Java的這一線(xiàn)程互斥的實(shí)現(xiàn)機(jī)制可以用一個(gè)最通俗的比方來(lái)說(shuō)明:比如公共衛(wèi)生間就是一個(gè)共享資源,每個(gè)人都可以使用,但又不能同時(shí)使用,所以衛(wèi)生間里有一把鎖。一個(gè)人進(jìn)去了,會(huì)把門(mén)鎖上,其他人就不能進(jìn)去。當(dāng)Ta出來(lái)的時(shí)候,要打開(kāi)鎖,下一個(gè)人才能繼續(xù)使用。
3、利用Synchronized關(guān)鍵字用于修飾同步互斥方法
(1)同步互斥方法
public synchronized void method(){
//允許訪(fǎng)問(wèn)控制的代碼
}
(2)同步互斥代碼塊
synchronized(syncObject){
//允許訪(fǎng)問(wèn)控制的代碼
}
(3)鎖定整個(gè)類(lèi)
public synchronized class SyncObject{
}
由于synchronized 塊可以針對(duì)任意的代碼塊,且可任意指定上鎖的對(duì)象,因此靈活性較高。但要注意:
l synchronized可以用來(lái)限定一個(gè)方法或一小段語(yǔ)句或整個(gè)類(lèi)(該類(lèi)中的所有方法都是synchronized方法)
l 將訪(fǎng)問(wèn)共享數(shù)據(jù)的代碼設(shè)計(jì)為synchronized方法
l 由于可以通過(guò) private 關(guān)鍵字來(lái)保證數(shù)據(jù)對(duì)象只能被方法訪(fǎng)問(wèn),所以只需針對(duì)方法提出一套同步鎖定機(jī)制。通過(guò)synchronized 方法來(lái)控制對(duì)類(lèi)中的成員變量(共享數(shù)據(jù))的訪(fǎng)問(wèn)。
l 編寫(xiě)線(xiàn)程安全的代碼會(huì)使系統(tǒng)的總體效率會(huì)降低,要適量使用
l 只有某一個(gè)線(xiàn)程的synchronized方法執(zhí)行完后其它線(xiàn)程的synchronized方法才能被執(zhí)行。
l 當(dāng)前時(shí)間,只有一個(gè)線(xiàn)程訪(fǎng)問(wèn)被鎖定的代碼段,但不能保證其他線(xiàn)程去訪(fǎng)問(wèn)其他沒(méi)有被鎖定的代碼段。因此所有對(duì)共享資源進(jìn)行操作的代碼段都應(yīng)該加鎖。
l 對(duì)數(shù)據(jù)庫(kù)操作時(shí),修改數(shù)據(jù)的線(xiàn)程要加鎖,而讀數(shù)據(jù)的線(xiàn)程可以不加鎖
有了這種解決方案,我們用線(xiàn)程安全的代碼來(lái)重新實(shí)現(xiàn)一下男孩和女孩取錢(qián)的故事。以下是核心代碼:
package com.px1987.j2se.thread.synchronous.v2;
/** 帳戶(hù)類(lèi) */
public class Account {
/** 余額 */
private int balance;
public Account(int balance) {
this.balance = balance;
}
}
package com.px1987.j2se.thread.synchronous.v2;
/** 男孩類(lèi),實(shí)現(xiàn)Runnable接口*/
public class Boy implements Runnable {
/** 銀行帳戶(hù)*/
Account account;
public Boy(Account account) {
this.account = account;
}
/** 男孩拿著折子去北京銀行海淀分行取錢(qián)*/
public void run() {
System.out.println(男孩拿著折子去北京銀行海淀分行取錢(qián));
synchronized (account) {
System.out.println(男孩走到柜臺(tái)錢(qián)詢(xún)問(wèn)帳戶(hù)余額);
int balance = account.getBalance();
System.out.println(銀行的業(yè)務(wù)員小姐親切地告訴他:您還有 +
balance + 元!。);
try {
System.out.println(男孩在思考要取多少錢(qián)呢?);
Thread.sleep(1);
System.out.println(男孩思考了1毫秒);
}
catch (InterruptedException e) {
e.printStackTrace();
}
int money = 5000;
System.out.println(男孩決定取 + money + 元);
System.out.println(銀行的業(yè)務(wù)員小姐為男孩辦理相關(guān)業(yè)務(wù)手續(xù));
account.setBalance(balance - money);
System.out.println(交易完成);
System.out.println(銀行的業(yè)務(wù)員小姐告訴男孩:您的余額為 +
account.getBalance()+ 元。);
}
System.out.println(男孩離開(kāi)銀行);
}
}
package com.px1987.j2se.thread.synchronous.v2;
/** 女孩類(lèi),實(shí)現(xiàn)runnable接口*/
public class Girl implements Runnable {
/** 女孩持有男孩的銀行卡*/
Account account;
public Girl(Account account) {
this.account = account;
}
/*** 女孩拿著小軍的銀行卡去西單百貨瘋狂購(gòu)物*/
public void run() {
String tabs = ;
System.out.println(tabs + 女孩拿著小軍的銀行卡去西單百貨瘋狂購(gòu)物);
System.out.println(tabs + 女孩看上了一件時(shí)髦的衣裳,準(zhǔn)備買(mǎi)下);
synchronized (account) {
System.out.println(tabs + 女孩到收銀臺(tái)準(zhǔn)備刷卡消費(fèi));
int balance = account.getBalance();
System.out.println(tabs + 收銀臺(tái)刷卡機(jī)讀取銀行卡余額為 + balance + 元);
int payout = 5000;
System.out.println(tabs + 女孩買(mǎi)衣服刷卡消費(fèi) + payout + 元);
account.setBalance(balance - payout);
System.out.println(tabs + 消費(fèi)清單打印出來(lái),消費(fèi): + payout + 元 + 余額:
+ account.getBalance() + 元);
}
System.out.println(tabs + 女孩離開(kāi)商場(chǎng));
}
}
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();
}
}
修改后的代碼運(yùn)行結(jié)果如下圖:
男孩拿著折子去北京銀行海淀分行取錢(qián)
女孩拿著小軍的銀行卡去西單百貨瘋狂購(gòu)物
女孩看上了一件時(shí)髦的衣裳,準(zhǔn)備買(mǎi)下
女孩到收銀臺(tái)準(zhǔn)備刷卡消費(fèi)
收銀臺(tái)刷卡機(jī)讀取銀行卡余額為10000元
女孩買(mǎi)衣服刷卡消費(fèi)5000元
消費(fèi)清單打印出來(lái),消費(fèi):5000元 余額:5000元
女孩離開(kāi)商場(chǎng)
男孩走到柜臺(tái)錢(qián)詢(xún)問(wèn)帳戶(hù)余額
銀行的業(yè)務(wù)員小姐親切地告訴他:您還有5000元!,
電腦資料
《Java線(xiàn)程及多線(xiàn)程技術(shù)及應(yīng)用》(http://www.lotusphilosophies.com)。男孩在思考要取多少錢(qián)呢?
男孩思考了1毫秒
男孩決定取5000元
銀行的業(yè)務(wù)員小姐為男孩辦理相關(guān)業(yè)務(wù)手續(xù)
交易完成
銀行的業(yè)務(wù)員小姐告訴男孩:您的余額為0元。
男孩離開(kāi)銀行
從結(jié)果中可以看出來(lái),男孩從查看余額到取錢(qián),女孩沒(méi)有操作帳戶(hù),所以最后的余額是正確的。
4、線(xiàn)程死鎖
使用互斥鎖容易產(chǎn)生死鎖問(wèn)題。比如:一個(gè)線(xiàn)程需要鎖定兩個(gè)對(duì)象才能完成,線(xiàn)程1擁有對(duì)象A的鎖,線(xiàn)程1如果再擁有對(duì)象B的鎖就能完成操作,線(xiàn)程2擁有對(duì)象B的鎖,線(xiàn)程2如果再擁有對(duì)象A的鎖就能完成操作。
很不幸的是線(xiàn)程1執(zhí)行不下去了,因?yàn)榫(xiàn)程1等待的資源對(duì)象B被線(xiàn)程2鎖住了,線(xiàn)程2也執(zhí)行不下去了,因?yàn)榫(xiàn)程2等待的資源對(duì)象A被線(xiàn)程1鎖住了,這樣就造成了死鎖。
閱讀一段文字:由多線(xiàn)程帶來(lái)的性能改善是以可靠性為代價(jià)的,主要是因?yàn)橛锌赡墚a(chǎn)生線(xiàn)程死鎖。死鎖是這樣一種情形:多個(gè)線(xiàn)程同時(shí)被阻塞,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。由于線(xiàn)程被無(wú)限期地阻塞,因此程序不能正常運(yùn)行。簡(jiǎn)單的說(shuō)就是:線(xiàn)程死鎖時(shí),第一個(gè)線(xiàn)程等待第二個(gè)線(xiàn)程釋放資源,而同時(shí)第二個(gè)線(xiàn)程又在等待第一個(gè)線(xiàn)程釋放資源。這里舉一個(gè)通俗的例子:如在人行道上兩個(gè)人迎面相遇,為了給對(duì)方讓道,兩人同時(shí)向一側(cè)邁出一步,雙方無(wú)法通過(guò),又同時(shí)向另一側(cè)邁出一步,這樣還是無(wú)法通過(guò)。假設(shè)這種情況一直持續(xù)下去,這樣就會(huì)發(fā)生死鎖現(xiàn)象。
導(dǎo)致死鎖的根源在于不適當(dāng)?shù)剡\(yùn)用“synchronized”關(guān)鍵詞來(lái)管理線(xiàn)程對(duì)特定對(duì)象的訪(fǎng)問(wèn)。“synchronized”關(guān)鍵詞的作用是,確保在某個(gè)時(shí)刻只有一個(gè)線(xiàn)程被允許執(zhí)行特定的代碼塊,因此,被允許執(zhí)行的線(xiàn)程首先必須擁有對(duì)變量或?qū)ο蟮呐潘栽L(fǎng)問(wèn)權(quán)。當(dāng)線(xiàn)程訪(fǎng)問(wèn)對(duì)象時(shí),線(xiàn)程會(huì)給對(duì)象加鎖,而這個(gè)鎖導(dǎo)致其它也想訪(fǎng)問(wèn)同一對(duì)象的線(xiàn)程被阻塞,直至第一個(gè)線(xiàn)程釋放它加在對(duì)象上的鎖。
(1)死鎖問(wèn)題的一個(gè)代碼示例
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獲得對(duì)象a的鎖);
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread1獲得對(duì)象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獲得對(duì)象b的鎖);
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread2獲得對(duì)象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();
}
}
下面是運(yùn)行結(jié)果:
(2)死鎖問(wèn)題的另一個(gè)代碼示例
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生產(chǎn)者消費(fèi)者問(wèn)題
1、生產(chǎn)者消費(fèi)者問(wèn)題的示例
生產(chǎn)者消費(fèi)者問(wèn)題也是一個(gè)典型的線(xiàn)程問(wèn)題。我們舉一個(gè)這方面的實(shí)例來(lái)說(shuō)明:在一個(gè)果園里,有農(nóng)夫和小孩,農(nóng)夫會(huì)不停的采摘水果放入果園中心的一個(gè)水果筐直到水果筐滿(mǎn),而小孩會(huì)不停的從水果筐里拿水果來(lái)吃,直到水果拿完。分析這個(gè)模型我們可以看出:農(nóng)夫可以看成是一個(gè)生產(chǎn)者的線(xiàn)程,小孩可以看成是一個(gè)消費(fèi)者的線(xiàn)程,而大水果筐是共享資源。
2、用Java程序表述的代碼示例
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 水果類(lèi)*/
public class Fruit {
/*** 水果編號(hào)*/
private int id;
/*** 水果編號(hào)計(jì)數(shù)器*/
private static int number = 0;
/*** 水果品種 */
private String variety;
/*** 水果品種數(shù)組 */
private String[] varietys = 蘋(píng)果,桃子,梨子,香蕉,西瓜,荔枝,葡萄.split(,);
public Fruit() {
super();
this.variety = varietys[new Random().nextInt(7)];
this.id = ++number;
}
}
水果筐應(yīng)該設(shè)計(jì)成類(lèi)似于棧的數(shù)據(jù)結(jié)構(gòu),其中包含一個(gè)數(shù)組來(lái)存放筐里的水果,而數(shù)組的下標(biāo)就是水果筐的容量。設(shè)定一個(gè)索引index表示指向下一個(gè)將要放入水果的位置。類(lèi)中的push方法模擬農(nóng)夫向水果筐中放入水果,pop方法模擬小孩從水果筐中拿水果。這兩個(gè)方法都要操作共享資源,所以push和pop方法都是同步互斥方法。
3、如何避免出現(xiàn)死鎖
那同步的問(wèn)題解決后是否會(huì)出現(xiàn)死鎖呢?大家試想一下,如果生產(chǎn)的速度大于消費(fèi)的速度就會(huì)導(dǎo)致功大于求,水果筐很容易就滿(mǎn)了,然而生產(chǎn)者又一直抱著水果筐不放,沒(méi)有機(jī)會(huì)給消費(fèi)者使用,消費(fèi)者不消費(fèi)生產(chǎn)者就無(wú)法生產(chǎn),所以就造成了死鎖。
怎樣解決呢?在兩個(gè)同步互斥方法中用到了wait和notify方法,這兩個(gè)方法是為了防止死鎖的。
l wait是Object類(lèi)的方法,它的作用是擁有互斥鎖的線(xiàn)程放棄鎖的使用權(quán),進(jìn)入wait池進(jìn)行等待,那么互斥鎖就有可能被其他線(xiàn)程獲得以執(zhí)行其他任務(wù)。
l notify也是Object類(lèi)的方法,它的作用是從wait池中喚醒一條正在等待的線(xiàn)程進(jìn)入就緒狀態(tài),被喚醒的這條線(xiàn)程就很可能重新獲得cup和互斥鎖來(lái)完成它的任務(wù)。
l notifyAll和Notify很相似,它是從wait池中喚醒所有正在等待的線(xiàn)程進(jìn)入就緒狀態(tài)。
需要注意的是以上三個(gè)方法都只能在synchronized方法中應(yīng)用,否者會(huì)出現(xiàn)下面的異常信息:IllegalMonitorStateException:current thread not owner。
4、實(shí)現(xiàn)的代碼示例
package com.px1987.j2se.thread.ProducerConsumer;
import java.text.DecimalFormat;
import java.util.Arrays;
/*** 水果框類(lèi),類(lèi)似一個(gè)棧的模型 */
public class FruitBasket {
/*** 容量為10的水果數(shù)組,也就是說(shuō)水果框最多能放下10個(gè)水果 */
private Fruit[] fruits = new Fruit[10];
/*** 下一個(gè)將要放入水果的位置*/
private int index = 0;
/*** 水果框中是否為空 @return true為空,false為不空 */
public boolean isEmpty() {
return index == 0 ? true : false;
}
/*** 水果框是否裝滿(mǎn)* @return true為滿(mǎn),false為未滿(mǎn)*/
public boolean isFull() {
return index == fruits.length ? true : false;
}
/*** 進(jìn)棧方法,模擬農(nóng)夫把水果放入筐中,@param name 農(nóng)夫的名字,@param fruit 水果對(duì)象 */
public synchronized void push(String name, Fruit fruit) {
//用while循環(huán),不用if,避免IndexOutOfBoundsException異常的產(chǎn)生
while (isFull()) {
//如果水果筐滿(mǎn)了,需要等待
try {
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
//將水果放入index指示的位置,index再上移一格
fruits[index++] = fruit;
System.out.println(name + 向水果框中放入編號(hào)為 + fruit.getId() + 的+
fruit.getVariety());
display();
this.notify(); //通知其他等待的農(nóng)夫或孩子可以開(kāi)始工作啦
}
/*** 出棧方法,模擬小孩從水果筐中取出水果,@param name 小孩的名字,@return 取出的水果*/
public synchronized Fruit pop(String name) {
//用while循環(huán),不用if,避免IndexOutOfBoundsException異常的產(chǎn)生
while (isEmpty()) {
try { //如果水果筐空,需要等待
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
Fruit fruit = null;
fruit = fruits[--index]; //index下移一位,取出指示位置上的水果
System.out.println(name + 從水果框中拿出編號(hào)為 + 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;
/** 果園里的農(nóng)夫類(lèi),他是生產(chǎn)者,實(shí)現(xiàn)Runnable接口*/
public class Farmer implements Runnable {
/** 姓名*/
private String name;
/** 水果框*/
private FruitBasket fruitBasket;
/** 農(nóng)夫會(huì)不停地重復(fù)這一系列動(dòng)作:從水果樹(shù)上采摘一個(gè)水果放入水果框中,然后隨機(jī)的休息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;
/*** 果園中的小孩類(lèi),他是消費(fèi)者,實(shí)現(xiàn)Runnable接口*/
public class Child implements Runnable {
/*** 姓名*/
private String name;
/*** 水果框*/
private FruitBasket fruitBasket;
/*** 小孩會(huì)不停地重復(fù)這一系列動(dòng)作:從水果框中拿出水果吃,然后隨機(jī)休息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;
/*** 果園中的小孩類(lèi),他是消費(fèi)者,實(shí)現(xiàn)Runnable接口*/
public class Child implements Runnable {
/*** 姓名*/
private String name;
/*** 水果框*/
private FruitBasket fruitBasket;
/*** 小孩會(huì)不停地重復(fù)這一系列動(dòng)作:從水果框中拿出水果吃,然后隨機(jī)休息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;
}
}
測(cè)試時(shí)使用多個(gè)生產(chǎn)者線(xiàn)程和多個(gè)消費(fèi)者線(xiàn)程
package com.px1987.j2se.thread.ProducerConsumer;
/** 果園類(lèi),測(cè)試*/
public class Orchard {
public static void main(String[] args) {
FruitBasket fruitBasket = new FruitBasket();
Thread farmerThread1 = new Thread(new Farmer(農(nóng)夫1, fruitBasket));
Thread farmerThread2 = new Thread(new Farmer(農(nóng)夫2, fruitBasket));
Thread farmerThread3 = new Thread(new Farmer(農(nóng)夫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();
}
}
程序的運(yùn)行結(jié)果如下:
農(nóng)夫1 向水果框中放入編號(hào)為1的蘋(píng)果
NO:01蘋(píng)果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫3 向水果框中放入編號(hào)為2的荔枝
NO:01蘋(píng)果 | NO:02荔枝 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩2 從水果框中拿出編號(hào)為2的荔枝
NO:01蘋(píng)果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫2 向水果框中放入編號(hào)為3的香蕉
NO:01蘋(píng)果 | NO:03香蕉 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩1 從水果框中拿出編號(hào)為3的香蕉
NO:01蘋(píng)果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩3 從水果框中拿出編號(hào)為1的蘋(píng)果
【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫2 向水果框中放入編號(hào)為4的蘋(píng)果
NO:04蘋(píng)果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩1 從水果框中拿出編號(hào)為4的蘋(píng)果
【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫1 向水果框中放入編號(hào)為5的蘋(píng)果
NO:05蘋(píng)果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫3 向水果框中放入編號(hào)為6的西瓜
NO:05蘋(píng)果 | NO:06西瓜 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫2 向水果框中放入編號(hào)為7的蘋(píng)果
NO:05蘋(píng)果 | NO:06西瓜 | NO:07蘋(píng)果 | 【4】 | 【5】 | 【6】 |
小孩3 從水果框中拿出編號(hào)為7的蘋(píng)果
NO:05蘋(píng)果 | NO:06西瓜 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩3 從水果框中拿出編號(hào)為6的西瓜
NO:05蘋(píng)果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩2 從水果框中拿出編號(hào)為5的蘋(píng)果
【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫2 向水果框中放入編號(hào)為8的桃子
NO:08桃子 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫1 向水果框中放入編號(hào)為9的荔枝
NO:08桃子 | NO:09荔枝 | 【3】 | 【4】 | 【5】 | 【6】 |
農(nóng)夫3 向水果框中放入編號(hào)為10的香蕉
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | 【4】 | 【5】 | 【6】 |
農(nóng)夫1 向水果框中放入編號(hào)為11的桃子
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | 【5】 | 【6】 |
農(nóng)夫1 向水果框中放入編號(hào)為12的荔枝
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 【6】 |
農(nóng)夫3 向水果框中放入編號(hào)為13的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:13西瓜 |
小孩1 從水果框中拿出編號(hào)為13的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 【6】 |
農(nóng)夫2 向水果框中放入編號(hào)為14的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:14西瓜 |
6.7 課后復(fù)習(xí)題
1、線(xiàn)程的應(yīng)用示例——繼承Thread類(lèi)
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(){ //重寫(xiě)run方法
while(true){
nowTime=new Date();
System.out.println(現(xiàn)在的時(shí)間為:+nowTime.getHours()+
:+nowTime.getMinutes()+:+nowTime.getSeconds());
try{
sleep(1000);
}
catch(InterruptedException e){
System.out.println(e.toString());
}
}
}
}
2、線(xiàn)程的應(yīng)用示例——實(shí)現(xiàn)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(){ //重寫(xiě)run方法
while(true){
nowTime=new Date();
System.out.println(現(xiàn)在的時(shí)間為:+nowTime.getHours()+
:+nowTime.getMinutes()+:+nowTime.getSeconds());
try{
timeThread.sleep(1000);
}
catch(InterruptedException e){
System.out.println(e.toString());
}
}
}
}
3、線(xiàn)程優(yōu)先級(jí)的應(yīng)用示例
class ThreadPriorityDemo extends Thread{
ThreadPriorityDemo(String strName){
System.out.println(線(xiàn)程名稱(chēng):+strName);
}
public void run(){
//打印出當(dāng)前線(xiàn)程的優(yōu)先級(jí)
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();
}
}