JAVA 多線程系統建立于 Thread 類,它的方法,它的共伴接口 Runnable 基礎上, Thread 類封裝了多線程的執行。為創建一個新的線程,你的程序必須實現 Runnabel 接口或繼承 Thread 類。
Thread 類定義了好幾個方法來幫助管理線程,本章用到的方法表如下:
方法
|
意義
|
getName |
獲得線程名 |
getPrionty |
獲得線程優先級 |
jsAlive |
判定線程是否仍運行 |
Join |
等侍一個線程終止 |
Run |
線程的入口點 |
Sleep |
在一段時候時掛起線程 |
Start |
通過調用運行的方法來啟動線程 |
主線程
任何一個 JAVA 都有一個主線程,當 JAVA 程序啟動時一個線程立刻運行,該線程通常叫做程序的主線程。因為它是程序開始執行時就運行,主線程的重要性體現在:
A、 通過它產生期它子線程
B、 通常它必須最后完成執行,因為它執行各種關閉動作。
盡管主線程在程序啟動時自動創建,但它可以由一個 Thread 對象控制。為此,必須調用 currentThread() 獲得它的一個引用, currentThread 是 Thread 類的公有的靜態成員:
?static Thread currentThread();
該方法返回一個調用它的線程的引用。一旦獲得主線程的引用,就可以像控制其它線程那樣控制主線程。
package com.jdk.thread;
public class CurrentThreadDemo1 {
?????? /**
?????? ?* @param args
?????? ?*/
?????? public static void main(String[] args) {
????????????? Thread t=Thread.currentThread();
????????????? System.out.println("current thread->"+t);
????????????? t.setName("main thread");
????????????? System.out.println("change after thread name->"+t);
????????????? try{
???????????????????? for(int i=5;i>0;i--){
??????????????????????????? System.out.println("i-->"+i);
??????????????????????????? t.sleep(1000);
???????????????????? }
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
?????? }
}
以上代碼是一個最簡單的 Thread 方法例子。
創建線程最簡單的辦法就是實現 Runnable 接口的類, Runnable 操象了一個執行代碼單元。為實現 Runnable 接口,一個類僅需實現 Run() 的簡單方法,該方法聲明如下:
public void run();
在 run 中可以定義代碼來構建新的線程, run 方法能夠像主線程那樣調用其它方法,引用其它類,聲明變量。僅有的不同是 run() 在程序中確立另一個并發程序的入口。當 run 返回時,該線程結束。這是一個例子程序:
package com.jdk.thread;
class newThread implements Runnable{
?????? Thread t;
??????
?????? public newThread(){
????????????? t=new Thread(this,"Demo Thread");
????????????? System.out.println("Client Thread ->"+t);
????????????? t.start();
?????? }
??????
?????? public void run() {
????????????? try{
???????????????????? for(int i=5;i>0;i--){
??????????????????????????? System.out.println("Client Thread->"+i);
??????????????????????????? Thread.sleep(500);
???????????????????? }
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(" 子線程結束 ");
?????? }
??????
}
public class CurrentThreadDemo2 {
?????? public static void main(String[] args) {
????????????? new newThread();
????????????? try{
???????????????????? for(int i=5;i>0;i--){
??????????????????????????? System.out.println("main Thread ->"+i);
??????????????????????????? Thread.sleep(1000);
???????????????????? }
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(" 主線程結束 ");
?????? }
}
通過該例子,我們應該知道 run() 方法是一個多線程的并發入口和結束方法,支持多線程的類必須要實現 Runnable 接口或繼承 Thread 類。通常主線程必須是結束運行的最后一個線程(舊版 JVM )。通常在支持多線程的類的構造函數中創建并啟動線程,創建線程就生成一個實現了 Runnable 接口的類,啟動線程就是調用該類的 start() 方法作用是運行 run() 方法。
下面是一個創建多線程的類:
package com.jdk.thread;
class ThreadDemo3 implements Runnable{
?????? String threadName;
?????? Thread t;
?????? public ThreadDemo3(String tname){
????????????? this.threadName=tname;
????????????? t=new Thread(this,threadName);
????????????? t.start();
?????? }
?????? public void run() {?
????????????? try{
???????????????????? for(int i=5;i>0;i--){
??????????????????????????? System.out.println(threadName+"-->"+i);
???????????????????? }
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(threadName+" 結束 !");
?????? }
??????
}
public class Thread3 {
??????
?????? /**
?????? ?* @param args
?????? ?*/
?????? public static void main(String[] args) {
????????????? ThreadDemo3 td1=new ThreadDemo3("one");
????????????? ThreadDemo3 td2=new ThreadDemo3("two");
????????????? ThreadDemo3 td3=new ThreadDemo3("three");
?????????????
????????????? System.out.println(" 主線程結束 !");
?????????????
?????? }
}
輸出結果是:
主線程結束
!
one-->5
one-->4
one-->3
one-->2
one-->1
one
結束
!
two-->5
two-->4
two-->3
two-->2
two-->1
two
結束
!
three-->5
three-->4
three-->3
three-->2
three-->1
three
結束
!
當把上代碼改成這樣:
package com.jdk.thread;
class ThreadDemo3 implements Runnable{
?????? String threadName;
?????? Thread t;
?????? public ThreadDemo3(String tname){
????????????? this.threadName=tname;
????????????? t=new Thread(this,threadName);
????????????? t.start();
?????? }
?????? public void run() {?
????????????? try{
???????????????????? for(int i=5;i>0;i--){
??????????????????????????? System.out.println(threadName+"-->"+i);
???????????????????? }
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(threadName+" 結束 !");
?????? }
??????
}
public class Thread3 {
??????
?????? /**
?????? ?* @param args
?????? ?*/
?????? public static void main(String[] args) {
????????????? ThreadDemo3 td1=new ThreadDemo3("one");
????????????? ThreadDemo3 td2=new ThreadDemo3("two");
????????????? ThreadDemo3 td3=new ThreadDemo3("three");
????????????? try{
???????????????????? Thread t=Thread.currentThread();
???????????????????? t.sleep(500);
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(" 主線程結束 !");
?????????????
?????? }
}
結果如下:
one-->5
one-->4
one-->3
one-->2
one-->1
one
結束
!
two-->5
two-->4
two-->3
two-->2
two-->1
two
結束
!
three-->5
three-->4
three-->3
three-->2
three-->1
three
結束
!
主線程結束
!
使用
isAlive
和
join
通過前面的例子可以看出我們是使用 sleep ()方法來延時,等待子線程結束,它帶來了一個更大的問題是一個線程怎么知道另一個線程已經結束呢?通常有兩種方法可以解決:
1、? 在線程中調用 isAlive (),這種方法由 Thread 定義,通常形式如下:
final Boolean isAlive();
如果調用的線程還在運行則返回
True
,否則返回
False;
但
isAlive
很少用到,等待線程結束通常使用的方法是調用
join()
。
2、? join() 形式如下:
final void join()throw InterruptedException
該方法等侍所調用的線程結束,該名字來自于要求線程等待直到指定的線程參與的概念。下面的例子是前面例子的改進,演示用 join() 以確保主線程最后結束??蓸右惭菔玖?/span> isAlive() 方法,代碼如下:
package com.jdk.thread;
class ThreadDemo3 implements Runnable{
?????? String threadName;
?????? Thread t;
?????? public ThreadDemo3(String tname){
????????????? this.threadName=tname;
????????????? t=new Thread(this,threadName);
????????????? t.start();
?????? }
?????? public void run() {?
????????????? try{
???????????????????? for(int i=5;i>0;i--){
??????????????????????????? System.out.println(threadName+"-->"+i);
???????????????????? }
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(threadName+" 結束 !");
?????? }
??????
}
public class Thread3 {
??????
?????? public static void main(String[] args) {
?????????????
????????????? ThreadDemo3 td1=new ThreadDemo3("one");
????????????? ThreadDemo3 td2=new ThreadDemo3("two");
????????????? ThreadDemo3 td3=new ThreadDemo3("three");
?????????????
????????????? System.out.println("td1 狀態 -->"+td1.t.isAlive());
????????????? System.out.println("td2 狀態 -->"+td2.t.isAlive());
????????????? System.out.println("td3 狀態 -->"+td3.t.isAlive());
?????????????
????????????? try{
???????????????????? System.out.println(" 等待子線程結束 !");
???????????????????? td1.t.join();
???????????????????? td2.t.join();
???????????????????? td3.t.join();
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
?????????????
????????????? System.out.println("td1 狀態 -->"+td1.t.isAlive());
????????????? System.out.println("td2 狀態 -->"+td2.t.isAlive());
????????????? System.out.println("td3 狀態 -->"+td3.t.isAlive());
?????????????
????????????? System.out.println(" 主線程結束 !");
?????? }
}
輸出的結果如下:
td1
狀態
-->true
td2
狀態
-->true
td3
狀態
-->true
等待子線程結束
!
one-->5
one-->4
one-->3
one-->2
one-->1
one
結束
!
two-->5
two-->4
two-->3
two-->2
two-->1
two
結束
!
three-->5
three-->4
three-->3
three-->2
three-->1
three
結束
!
td1
狀態
-->false
td2
狀態
-->false
td3
狀態
-->false
主線程結束
!
線程的優先級別
通常優先級別高的線程比優先級別低的線程獲得更多 CPU 時間,但實際與這與很多因素有關。就不要管這么多了,懂怎么設置線程的優選級別就 OK 。設置線程優選級別的方法如下:
final void setPriority(int level) ; 該方法是 Thread 成員方法。 Level 的值通常是 1 到 10 ,也就是
MIN_PRIORITY 到 MAX_PRIORITY 的范圍內。
線程同步
當兩個或兩個以上的線程要共享一個資源是,它們需要某種方法來確定同一時刻只能有一個線程占用資源。達到此目的的過程叫做同步 (synchronization) 。
使用同步方法,當一個線程正在同步方法內所有要訪問該同步方法的線程必須等侍,一個例子:
package com.jdk.thread;
class Callme{
??????
?????? public void call(String msg){
????????????? System.out.print("["+msg);
????????????? try{
???????????????????? Thread.sleep(1000);
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println("]");
?????? }
}
class Caller implements Runnable{
?????? String msg;
?????? Thread t;
?????? Callme target;
??????
?????? public Caller(Callme targ,String s){
????????????? target=targ;
????????????? msg=s;
????????????? t=new Thread(this);
????????????? t.start();
?????? }
?????? public void run() {
????????????? target.call(msg);
?????? }
}
public class SynchDemo {
?????? public static void main(String[] args) {
????????????? Callme callme=new Callme();
????????????? Caller c1=new Caller(callme,"Hello"); // 通過多線程創建對象
????????????? Caller c2=new Caller(callme,"JAVA");
????????????? Caller c3=new Caller(callme,"C++");
????????????? try{
???????????????????? c1.t.join();
???????????????????? c2.t.join();
???????????????????? c3.t.join();
????????????? }catch(Exception e){
???????????????????? e.printStackTrace();
????????????? }
????????????? System.out.println(" 主線程結束 !");
?????? }
}
輸出的結果是:
[Hello[JAVA[C++]
]
]
主線程結束
!
主線程結束
!
在這里必須要記住多線程的同步訪問,只指對同一個對象,也就是說只有同時多個線程訪問一個對象的數據或方法時才會出現以上這種情況。因為多線程的 Caller 類,同時訪問了同一個對象 callme 的同一個方法 call() 所以就出現以上的輸了結果,顯然這不是我們想要的。
如果我們在 Callme 類的 call ()方法前加上 synchronized 方法如下:
?? public synchronized void call() ;
得到的結束就是 :
[Hello]
[JAVA]
[C++]
主線程結束 !
線程間的通訊
為了避免輪詢, JAVA 包含了通過 wait() , notify() 和 notifyAll() 實現一個進程間的通信機制。這些方法在對象中是用 final 的方法實現的。所以所有的類都含有它們,這三個方法僅在 synchronized 方法中可以調用。
Wait() 告知被調用的線程放棄管程,進入睡眠直到其它線程進入相同的管程并調用 notify() 。
Notify() 恢復相同對象中第一個調用 wait() 的線程。
notifyAll() 恢復相同對象中所有調用 wait() 的線程,具有最高優先級的最先運行。
這些方法在 Object 類中被聲明如下:
public void wait() throws InterruptedException
public void waitAll();
public void notify();
下面是一個不使用經程間通信的例程,它輸入的結果并不是我們想要的,代碼如下:
package com.jdk.thread;
class Q{
?????? int n;
?????? synchronized int get(){
????????????? System.out.println("get "+n);
????????????? return n;
?????? }
?????? synchronized void put(int vn){
????????????? this.n=vn;
????????????? System.out.println("put "+n);
?????? }
}
class Producer implements Runnable{
?????? Q q;
?????? public Producer(Q vq){
????????????? this.q=vq;
????????????? new Thread(this,"Producer").start();
?????? }
??????
?????? public void run() {
????????????? int i=0;
????????????? while(true){
???????????????????? q.put(i++);
????????????? }
?????? }
??????
}
class Consumer implements Runnable{
??????
?????? Q q;
?????? public Consumer(Q vq){
????????????? this.q=vq;
????????????? new Thread(this,"Consumer").start();
?????? }
?????? public void run() {
?????????????
????????????? while(true){
???????????????????? q.get();
????????????? }
?????? }
??????
}
public class SynchDemo1 {
?????? public static void main(String[] args) {
????????????? Q q=new Q();
????????????? new Producer(q);
????????????? new Consumer(q);
????????????? System.out.println(" 主線程結束 !");
?????? }
}
盡管在
Q
類中的
put
()和
get()
方法是同步的,沒有東西阻止生產者超越消費者。也沒有東西阻止消費者消費同樣序列多次,所以輸出結果如下:
get 19000
put 19001
get 19001
put 19002
get 19002
通常把 Q 類改成如下,但我還是有點不理解,為什么輸出結果不是從 1 開始呢?進程中的線程是怎么通信的呢?
class Q{
?????? int n;
?????? boolean valueSet=false;
?????? synchronized int get(){
????????????? if(!valueSet){
???????????????????? try{
??????????????????????????? wait();
???????????????????? }catch(Exception e){
??????????????????????????? e.printStackTrace();
???????????????????? }
????????????? }
????????????? System.out.println("get "+n);
????????????? valueSet=false;
????????????? notify();
????????????? return n;
?????? }
?????? synchronized void put(int vn){
????????????? if(valueSet){
???????????????????? try{
??????????????????????????? wait();
???????????????????? }catch(Exception e){
??????????????????????????? e.printStackTrace();
???????????????????? }
????????????? }
????????????? this.n=vn;
????????????? valueSet=true;
????????????? System.out.println("put "+n);
????????????? notify();
?????? }
}
死鎖
需要避免的與多任務和理有關的特殊的錯誤類型是死鎖,死鎖發生在當兩個線程對一對同步對象有循環的依懶關系時。
線程掛起、恢復、終止