Chapter2: Thread Creation and Management
2.1 What Is a Thread?
介紹了什么是線程,以及線程(thread, multithread)與進(jìn)程(process,
mutltitask)的區(qū)別。其中的一個(gè)重要區(qū)別是對(duì)共享數(shù)據(jù)的訪問(wèn)。進(jìn)程可以共享操作系統(tǒng)級(jí)別的某些數(shù)據(jù)區(qū)域,如剪貼板;而線程是對(duì)程序自有的數(shù)據(jù)進(jìn)
行共享。進(jìn)程之間對(duì)共享數(shù)據(jù)的存取方法是特殊的,因此自然能夠得到程序員的注意;而線程之間對(duì)共享數(shù)據(jù)的存取與對(duì)線程自己的數(shù)據(jù)的存取方法是一樣的,所以
常常比較隱蔽,而被程序員忽略。其實(shí),這也是多線程開(kāi)發(fā)比較難的地方。所以,這一節(jié)最后說(shuō):"A thread, then, is a discrete task that operates on data shared with other threads.(線程就是一個(gè)在與其它線程共享的數(shù)據(jù)上進(jìn)行操作的單獨(dú)任務(wù)。)"
2.2 Creating a Thread
1. 有兩種方法創(chuàng)建線程:使用Thread類或者Runnable接口
2. 示例程序竟然是編一個(gè)打字游戲,所以,這本書的門檻還是有點(diǎn)高的:(。當(dāng)然可以從該書的站點(diǎn)直接下載代碼。
3. Thread class
package java.lang;
public class Thread implements Runnable {
public Thread( );
public Thread(Runnable target);
public Thread(ThreadGroup group, Runnable target);
public Thread(String name);
public Thread(ThreadGroup group, String name);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize);
public void start( );
public void run( );
}
Thread name: 線程名,用以打印線程信息時(shí)用,缺省為Thread-N。
Runnable target:可運(yùn)行的目標(biāo)。一個(gè)可運(yùn)行的對(duì)象就是一串可由線程執(zhí)行的指令。缺省就是在run()方法中編寫的內(nèi)容。
Thread group:線程組。對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)沒(méi)什么意義,缺省在同一組。
Stack size:線程堆棧大小,與平臺(tái)相關(guān)。為了使程序更具可移植性,并不推薦使用。
2.3 The Lifecycle of a Thread
1. Creating a Thread:創(chuàng)建一個(gè)線程對(duì)象,并沒(méi)有開(kāi)始執(zhí)行一個(gè)線程
2. Starting a Thread:在調(diào)用線程對(duì)象的start()方法前,一直處于等待狀態(tài)。可以通過(guò)調(diào)用isAlive()方法來(lái)獲取線程是否正在運(yùn)行;
3. Terminating a Thread:線程在以下情況下結(jié)束
1)執(zhí)行完run()中的語(yǔ)句;
2)拋出一個(gè)異常,包括其沒(méi)有捕獲的異常;
3)其所在的程序(或者容器)調(diào)用System.exit()。
注意:一個(gè)線程結(jié)束后,就不能再次啟動(dòng),該線程就變成一個(gè)普通的對(duì)象,如果試圖再次調(diào)用start()方法,將拋出java.lang.IllegalThreadStateException異常。如果想多次運(yùn)行,要么創(chuàng)建新的對(duì)象;要么就是不要結(jié)束該線程。
4. Java并沒(méi)有提供可靠的方法來(lái)暫停、掛起或者重啟一個(gè)線程的方法,線程本身能通過(guò)sleep()函數(shù)來(lái)暫停執(zhí)行。目前依然只能確保若干毫秒級(jí)別的精度。
5. Thread Cleanup:線程終止后,線程對(duì)象依然存在。可以通過(guò)join()方法以阻塞方式(blocked)來(lái)等待某個(gè)線程終止。
2.4 Two Approches to Stop a Thread
1. Setting a Flag:在線程的執(zhí)行過(guò)程中判斷其它線程是否將標(biāo)志置位。其缺點(diǎn)是有時(shí)間延遲,尤其是當(dāng)進(jìn)入了某些被阻塞的函數(shù)如:sleep(), wait()等;
2. 調(diào)用線程的interrupt()方法,
線程的執(zhí)行過(guò)程中改為判斷isInterrupted()的返回值。可以解決線程進(jìn)入阻塞導(dǎo)致的延遲,但依然解決不了超長(zhǎng)時(shí)間計(jì)算而導(dǎo)致的延遲。
interrupt()有兩個(gè)作用:1)終止sleep()和wait(),拋出InterruptedException;2)使
isInterrupted()返回true。下面這段代碼將演示interrupt()的作用:
public class TestInterrupted {
public static void main(String args[]) throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread.currentThread().sleep(100); //注釋掉這句有什么影響呢?
System.out.println("before interrupt");
foo.interrupt();
System.out.println("after interrupt");
} }
class Foo extends Thread {
public void run() {
try{
while(!isInterrupted()) {
System.out.println("start calculating...");
double pi = 3.1415926;
for(int i=0; i<5; i++) {
for(long j=0; j<1000000; j++) {
pi *= pi;
}
System.out.println("i="+i);
}
System.out.println("before sleep");
sleep(5000); //注釋掉這句及相關(guān)的try...catch語(yǔ)句,又怎樣呢?
System.out.println("after sleep");
}
} catch(InterruptedException ex) {
ex.printStackTrace(System.out);
}
}
}
2.5 The Runnable Interface
為什么有了Thread類,還要有Runnable接口呢?最主要的原因是為了解決Java不能多重繼承的問(wèn)題。線程繼承自
Thread,還是僅僅實(shí)現(xiàn)Runnable接口,取決于這個(gè)線程的作用。不過(guò),如果僅僅實(shí)現(xiàn)Runnable接口,則在線程里不能使用Thread類的
方法,比如interrupt()和isInterrupted()。當(dāng)然,這個(gè)還是要取決于實(shí)際情況。
2.6 Threads and Objects
主要講解線程與對(duì)象的關(guān)系。線程根本上還是個(gè)對(duì)象,其可以存取任何對(duì)象包括其它線程對(duì)象,當(dāng)然也能被其它對(duì)象存取,只要沒(méi)有因
為爭(zhēng)奪公共資源而被鎖定,線程對(duì)象中的任何屬性和方法都有可能同時(shí)執(zhí)行。并且Java中的鎖只針對(duì)對(duì)象本身,并不包括對(duì)象下的屬性;而對(duì)方法同步,則等同
于對(duì)對(duì)象同步。更進(jìn)一步的說(shuō)明還可以參考《Practical
Java》中的"實(shí)踐46:面對(duì)instance函數(shù),synchronized鎖定的是對(duì)象(object)而非函數(shù)(methods)或代碼
(code)"。下面用兩段代碼來(lái)說(shuō)明一下:
代碼1:
public class TestLock {
public static void main(String args[])
throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}
class Foo extends Thread{
protected int arrI[] = new int[10];
public void run() {
try {
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
} catch(InterruptedException ex) {}
}
public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
public void setInt2(String from, int pos, int val)
throws InterruptedException {
synchronized(arrI){
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
}
結(jié)果:非線程安全,setInt()在對(duì)象上加鎖,而setInt2()在屬性arrI上加鎖,不同的鎖不能保證線程安全。可能的結(jié)果如下:
Foo:arrI[0]=0
Main:arrI[0]=0
Main:arrI[1]=21
Main:arrI[2]=22
Foo:arrI[1]=21
Main:arrI[3]=23
Main:arrI[4]=24
Main:arrI[5]=25
Foo:arrI[2]=2
Main:arrI[6]=26
Main:arrI[7]=27
Foo:arrI[3]=3
Foo:arrI[4]=4
Main:arrI[8]=28
Main:arrI[9]=29
Foo:arrI[5]=5
Foo:arrI[6]=6
Foo:arrI[7]=7
Foo:arrI[8]=8
Foo:arrI[9]=9
代碼2:
public class TestLock1 {
public static void main(String args[])
throws InterruptedException{
Foo1 foo = new Foo1();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}
class Foo1 extends Thread{
protected int arrI[] = new int[10];
public void run() {
try{
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
}catch(InterruptedException ex){}
}
public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
public synchronized void setInt2(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
結(jié)果:線程安全