1、使用線程主要有以下原因:1)解決生成進程導致的性能問題;2)用於同時處理;3)合理利用CPU資源。
2、Java 線程的運行:構造一個Thread類的實例(主要有兩種方法),調用其start()方法,如:
Thread t = new Thread();
t.start();
- 1
- 2
- 1
- 2
這是一個空殼線程,不做任何事,創建之後就退出。
構造一個Thread類的實例的兩種方法:1)派生Thread的子類,覆蓋run()方法;2)實現一個Runnable接口,將Runnable對象傳遞給Thread構造函數。採用方法2)更能區分線程和線程執行的任務。
3、(派生,extends)Thread類有多個方法,採用派生Thread並覆蓋run()方法時,不能覆蓋其他方法,如start(),stop(),interrupt(),join(),sleep()等等。簡單示例:
public class MyThread extends Thread {
private String msg;
public MyThread(String s) {
msg = s;
}
public void run() {
System.out.println(msg);
}
}
public static void main(String[] args) {
String s = "Hello world.";
MyThread t = new MyThread(s);
t.start();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
當然這個示例也沒有實際應用,因爲沒有利用多線程完成I/O和CPU之間的平衡問題。僅作爲示例
4、(實現接口,implements)爲避免覆蓋標準Thread中的其他方法,可以將任務編寫爲Runnable的一個實例。將上述代碼修改一下:
public class MyRunnable implements Runnable {
private String msg;
public MyRunnable(String s) {
msg = s;
}
public void run() {
System.out.println(msg);
}
}
public static void main(String[] args) {
String s = "Hello world.";
MyRunnable mr = new MyRunnable(s);
Thread t = new Thread(mr);
t.start();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
這樣就能創建線程了。
那線程之間如何交互?
5、Thread類中run()和start()方法既不接收參數也不返回結果。線程接收參數容易實現,不管是派生子類還是實現Runnable接口,都可以在構造函數中將參數傳遞進去;那線程運行的結構如何返回?
不好的方法——1):派生子類中實現一個public type[] getResult()的方法,這樣在線程運行結束的時候得到結果。問題在於不知線程結束時間,當調用getResult()的時候,也許run()還沒有運行完。
不太好的方法——2):輪詢,既當run()方法調用結束後,修改子類某字段或者是有個標誌。時間效率太差。
比較好的方法——3):回調。要想獲得線程運行之後的結果,不一定去請求線程的方法,也可以用線程去通知主程序。回調也有兩種方法,一是靜態方法,二是回調實例方法。靜態方法雖然簡單,但是不夠靈活。以下假如主程序通過調用其它線程分析字符串中的計算式子,得到結果,並打印。
調用靜態方法的僞代碼:
public class CallBack implements Runnable {
private String source;
private String result;
public CallBack(String s) {
this.source = s;
}
public void run() {
try{
...
result = ...;
CallBackUserInterface.receiveResult(source, result);
}
catch(Exception e) {
...
}
}
}
public class CallBackUserInterface {
public static void receiveResult(String source, String result) {
Sytem.out.println(source + " = " + result);
}
public static void main(String[] args) {
for(int i = 0; i < args.length; i++) {
CallBack cb = new CallBack(args[i]);
Thread t = new Thread(cb);
t.start();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
而調用實例方法的僞代碼:僅列出改變的地方
public class InstanceCallBack implenments Runnable {
private String source;
private String result;
private InstanceCallBackUserInterface callBack;
public CallBack(String s, InstanceCallBackUserInterface callBack) {
this.source = s;
this.callBack = callBack;
}
public void run() {
try{
...
result = ...;
//Here
callBack.receiveResult(result);
}
catch(Exception e) {
...
}
}
}
public class InstanceCallBackUserInterface {
private String source;
private String result;
public InstanceCallBackUserInterface(String source) {
this.source = source;
}
public void calculate() {
InstanceCallBack cb = new InstanceCallBack(souece, this);
Thread t = new Thread(cb);
t.start();
//Here
void receiveResult(String result) {
this.result = result;
}
public String toString() {
String s = source + " = " + result;
return s;
}
public static void main(String[] args) {
for(int i = 0; i < args.length; i++) {
InstanceCallBackUserInterface c = new InstanceCallBackUserInterface(args[i]);
c.calculate();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
這裏沒有將calculate()方法放在構造方法裏,主要是擔心構造方法未完成calculate裏的線程便執行結束。
回調實例函數雖然有點複雜,但是多個類都關心線程運行結果時,回調靜態函數就無能爲力了,而如果多個類都實現了一個統一的接口用於接收結果,則可以向多個對象發送運行結果。
public interface ResultListener {
public void receiveResult(String result);
}
public class ListCallBack implements Runnable {
List listenerList;
private String source;
private String result;
public synchronized void addListener(ResultListener l) {
listenerList.add(l);
}
public synchronized void removeListener(ResultListener l) {
listenerList.remove(l);
}
public synchronized sendResult() {
ListIterator iterator = listenerList.listIterator();
while(iterator.hasNext()) {
ResultListener rl = (ResultListener)iterator.next();
rl.receiveResult(result);
}
}
public void run() {
...
sendResult();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
調用者類先構造一個ListCallBack,並將自己添加到listenerList中,再運行線程。
6、同步問題。由synchronized關鍵詞修飾,可修飾對象或者方法。
而同步容易導致死鎖,所以同步儘量少使用。同步使用的情景一般有全局變量,所以減少全局變量可以使同步減少。
7、線程調度。
線程優先級有1~10,數字大優先級高,與一般情況不同。Thread中有getPriority()和setPriority()兩個方法。
線程調度的時機:1)阻塞;2)顯示放棄(Thread的yield()方法);3)休眠(Thread 的sleep()方法,精度依賴平臺);4)連接線程,Thread的join()方法,某線程a在其代碼中調用線程t的join()方法,其後的代碼要在線程t運行結束再運行;5)等待一個對象,調用Object的wait()方法,每個對象都有,當此對象的notify()方法調用之後,運行wait()之後的代碼,另外wait()方法也可以傳入時間參數,即等待一段時間就運行其後代碼;6)當線程結束也進行線程調度。而調用sleep,join,wait方法都有被中斷(interrupt)的可能,一旦捕獲到中斷異常,則執行catch(InterruptException
ex)裏的代碼。
8、線程池。
儘管線程與進程相比開銷小很多,但是頻繁創建與刪除依然影響性能。雖然線程停止之後不能重啓,但是可以進行改造。首先保存一個全局的任務池並創建固定數量的線程。當池爲空時,線程等待;當向池中添加一個任務時,所有等待線程得到通知。當一個線程結束其分配的任務時,再去從池中獲取新的任務。
需要注意的是,如何告知程序已經結束。一般需要再設置兩個全局變量,一個是總任務數,一個是已經完成的任務數。