java基础----线程
一、進程與線程
進程(process)是一個可并發執行的具有獨立功能的程序(program)關于某個數據集合的一次執行過程,也是操作系統進行資源分配和保護的基本單位。
線程(thread)是操作系統進程中能夠獨立執行的實體(控制流),是處理器調度和分派的基本單位。
比如word進程中有拼寫檢查、語法檢查很多線程。
二、Java的線程實現
????????在Java中如果要想進行多線程代碼的實現有兩種方式:
??????????????????·繼承Thread類
??????????????????·實現Runnable接口
1.1、繼承Thread類
Thread類聲明如下
public class Thread extends Object implements Runnable {public Thread() //構造方法public Thread(String name) //name指定線程名public Thread(Runnable target) //target指定線程的目標對象public Thread(Runnable target, String name)public void run() //描述線程操作的線程體public final String getName() //返回線程名public final void setName(String name) //設置線程名public static int activeCount() //返回當前活動線程個數public static Thread currentThread() //返回當前執行線程對象public Sting toString() //返回線程的字符串信息,包括名字、優先級和線程組public synchronized void start() //啟動已創建的線程對象 }當一個類需要按照多線程的方式處理時,可以讓這個類直接繼承自Thread類即可,而且繼承的時候要覆寫好Thread類中提供的run()方法:
//聲明自定義線程類繼承Thread類實現奇數/偶數序列線程并發執行。public class NumberThread extends Thread {private int k; // 序列初始值public NumberThread(String name, int k) {super(name);this.k = k;}public NumberThread(String name) {this(name, 0);}public void run() // 覆蓋run方法的線程體{int i = k;System.out.print("\n" + this.getName() + ": ");while (i < 50) {System.out.print(i + " ");i += 2;}System.out.println(this.getName() + "結束!");}public static void main(String args[]) {NumberThread thread_odd = new NumberThread("奇數線程", 1); // 創建線程對象NumberThread thread_even = new NumberThread("偶數線程", 2);// thread_odd.setPriority(10); //設置優先級為最高thread_odd.start(); // 啟動線程對象thread_even.start();System.out.println("currentThread=" + Thread.currentThread().getName()); // 獲得當前線程對象名System.out.println("activeCount=" + thread_even.activeCount());System.out.println("main Priority="+ Thread.currentThread().getPriority()); // 獲得當前線程對象的優先級} }啟動一個線程并不是依靠run()方法而是start()方法。此時通過start()方法執行線程的操作,操作中可以發現,每一個線程間都屬于交替的運行狀態,即:所有的線程都是交替運行的,且:那個線程搶到了CPU資源,那個線程就執行。
1.2實現Runnable接口
Runnable接口定義如下:
public interfaceRunnable{public void run() ;}線程實現的第二種手段,實現Runnable接口來實現線程的操作類,???
//聲明自定義線程類實現Runnable接口實現奇數/偶數序列線程并發執行。public class NumberRunnable implements Runnable {private int k;public NumberRunnable(int k) {this.k = k;}public NumberRunnable() {this(0);}public void run() {int i = k;System.out.println();while (i < 50) {System.out.print(i + " ");i += 2;}System.out.println("結束!");}public static void main(String args[]) {NumberRunnable odd = new NumberRunnable(1); // 創建具有線程體的目標對象Thread thread_odd = new Thread(odd, "奇數線程"); // 以目標對象創建線程對象thread_odd.start();new Thread(new NumberRunnable(2), "偶數線程").start();System.out.println("currentThread=" + Thread.currentThread().getName());// 獲得當前線程對象名System.out.println("activeCount=" + Thread.activeCount());} }線程確實已經實現了,但是需要注意的是,如果要想啟動一個線程肯定是Thread類中的start()方法完成,觀察Thread類中提供的構造方法:public Thread(Runnable target)通過構造發現,Thread類可以接收Runnable子類的對象,所以一切的線程都可以通過Thread類進行啟動。此時,通過Thread類進行了線程的啟動。
1.3、兩種實現方式的區別
????????對于Thread類和Runnable接口本身都是可以進行多線程的實現,那么兩者到底該使用誰更好呢?
??????????????1、??繼承局限:使用Runnable接口可以避免單繼承的局限,而Thread類則有此局限;
????????????? 2、??資源共享:使用Runnable接口實現多線程,可以實現資源(對象屬性)的共享,而Thread類卻無法實現。
??????????????????????????? |-此點只是相對而言,因為兩者的此種區別是有其應用范圍的。
范例:觀察資源共享
class MyThreadextends Thread {private int count = 5 ;public void run(){for(int x=0;x<50;x++){if(this.count>0){System.out.println("count= " + this.count--) ;}}}};public classThreadDemo03 {public static void main(String args[]){new MyThread().start() ;new MyThread().start() ;new MyThread().start() ;}};現在的程序中每一個線程都各自占有各自的count屬性,所以并沒有達到資源共享的目的,如果換成Runnable呢?
class MyThread implements Runnable {private int count = 5 ;public void run(){for(int x=0;x<50;x++){if(this.count>0){System.out.println("count= " + this.count--) ;}}}};public classThreadDemo04 {public static void main(String args[]){MyThreadmt = new MyThread() ;new Thread(mt).start() ;new Thread(mt).start() ;new Thread(mt).start() ;}};現在的代碼中可以發現,count屬性已經被所有的線程對象所共同擁有了。
class MyThread implements Runnable {private int count = 5 ;public void run(){for(int x=0;x<50;x++){if(this.count>0){System.out.println("count= " + this.count--) ;}}}};public classThreadDemo04 {public static void main(String args[]){MyThreadmt1= new MyThread() ;MyThread mt2 = new MyThread() ;MyThread mt3 = new MyThread() ;new Thread(mt1).start() ;new Thread(mt2).start() ;new Thread(mt3).start() ;}};現在的程序中每一個線程都各自占有各自的count屬性,
1.4、兩種實現方式的聯系
?從Thread類和Runnable接口中都可以發現,都必須同時覆寫run()方法,兩者的關系如何呢?觀察Thread類的定義:
public class Thread extends Object implements Runnable
其中有個Runable類型的變量
/* What will be run. */private Runnable target;其中的run方法定義如下:
?public void run() // 描述線程操作的線程體{if (target != null)target.run(); // 執行目標對象的run()方法}}發現Thread類實際上是Runnable的子類。而且Thread類也要去接收Runnable其他子類的對象,而且所有的線程中,通過Runnable接口實現的線程類里面都是編寫的具體功能,而并沒有所謂的CPU調度,而真正意義上的CPU調度由操作系統完成(通過Thread類的start()方法調用的)。
Thread類要去協調操作系統,并且最終還要執行具體的線程主體的方法,而線程的主體呢,現在只專著于具體的功能實現,至于如何調度根本不管。Thread代理自定義的線程類的對象,如圖所示:
從圖的關系上可以清楚的發現,現在在線程中應用的設計思路就是代理設計模式。
三、線程的狀態
1、當執行new Thread(Runnable r)后,新創建出來的線程處于new狀態,這種線程不可能執行
2、當執行thread.start()后,線程處于runnable狀態,這種情況下只要得到CPU,就可以開始執行了。runnable狀態的線程,會接受JVM的調度,進入running狀態,但是具體何時會進入這個狀態,是隨機不可知的
3、running狀態中的線程最為復雜,可能會進入runnable、waiting、timed_waiting、blocked、dead狀態
如果CPU調度給了別的線程,或者執行了Thread.yield()方法,則進入runnable狀態,但是也有可能立刻又進入running狀態
 如果執行了Thread.sleep(long),或者thread.join(long),或者在鎖對象上調用object.wait(long)方法,則會進入timed_waiting狀態
 如果執行了thread.join(),或者在鎖對象上調用了object.wait()方法,則會進入waiting狀態 
 如果進入了同步方法或者同步代碼塊,沒有獲取鎖對象的話,則會進入blocked狀態 
4、處于waiting狀態中的線程,如果是因為thread.join()方法進入等待的話,在目標thread執行完畢之后,會回到runnable狀態;如果是因為object.wait()方法進入等待的話,在鎖對象執行object.notify()或者object.notifyAll()之后會回到runnable狀態
處于timed_waiting狀態中的線程,和waiting狀態中的差不多,只不過是設定時間到了,就會回到runnable狀態
5、處于blocked狀態中的線程,只有獲取了鎖之后,才會脫離阻塞狀態
6、當線程執行完畢,或者拋出了未捕獲的異常之后,會進入dead狀態,該線程結束
四、線程的操作方法
4.1、命名和取得
????????每一個線程實際上都可以為其設置名字,而且也可以取得每一個線程的名字:
??????????????????·設置線程名稱:public final void setName(String name)
??????????????????·取得線程名稱:public final String getName()
??????????????????·取得當前線程:public static Thread currentThread()
??????????????????? 返回當前活動線程個數 public static int activeCount()
?????? 除了以上的設置名稱的方法外,在Thread類中也提供了兩個構造方法:
??????????????????·public Thread(String name)
??????????????????·public Thread(Runnable target,String name)
?每次java運行的時候,實際上都會啟動一個JVM的進程。那么既然是多線程的處理機制,實際上主方法是在一個JVM上產生的一個線程而已。
4.2、線程的休眠
所謂的休眠就是指減緩程序的運行速度,如果要休眠使用如下的方法:
??????????·休眠:public static void sleep(long millis) throwsInterruptedException,指定休眠時間
比如Thread.sleep(1000),當前線程睡眠1秒。需要知道的是,1秒后,線程是回到可執行狀態,并不是執行狀態,什么時候執行那是由虛擬機來決定的。所以sleep(1000)并不是在睡眠1秒后立即執行。
4.3、線程的優先級
????????實際上所有的線程啟動之后并不是立刻運行的,都需要等待CPU進行調度,但是調度的時候本身也是存在“優先”級的,如果優先級高則有可能最先被執行。
????????如果要想設置優先級可以使用:publicfinal void setPriority(int newPriority)
????????這個優先級需要接收一個整型的數字,這個數字只能設置三個內容:
??????????????????·最高優先級:public static final int MAX_PRIORITY??????? 1
??????????????????·中等優先級:public static final int NORM_PRIORITY?????? 5
??????????????????·最低優先級:public static final int MIN_PRIORITY????????10
問題:主方法的優先級是什么?
public classMainDemo {public static void main(String args[]){System.out.println(Thread.currentThread().getPriority()); //5System.out.println("MAX_PRIORITY" + Thread.MAX_PRIORITY) ;//10System.out.println("MIN_PRIORITY" + Thread.MIN_PRIORITY) ;//1System.out.println("NORM_PRIORITY" + Thread.NORM_PRIORITY) ;//5}};主方法屬于中等優先級。
4.4、? join方法
線程有join方法,當線程1調用線程2的join方法時,線程1必須等待線程2執行完畢,線程1才能繼續往下執行。join方法主要用來將大問題分解成小問題,當小問題計算完成時,大問題才能繼續往下執行,這時候我們就可以利用join方法了。下面演示了一下線程的join方法。
public class TestJoin {public static void main(String[] args) {for(int i =0;i<10;i++){System.out.println(Thread.currentThread().getName()+" "+i);if(i==2){JoinThread jt=new JoinThread();jt.start();//主線程調用jt的join方法,主線程必須等待jt線程執行完才能繼續執行try {jt.join();} catch (InterruptedException e) {e.printStackTrace();}}}}}class JoinThread extends Thread {@Overridepublic void run() {for(int i = 0;i<10;i++){System.out.println(this.getName()+" "+i);}}}運行結果如下:
main? 0
 main? 1
 main? 2
 Thread-0? 0
 Thread-0? 1
 Thread-0? 2
 Thread-0? 3
 Thread-0? 4
 Thread-0? 5
 Thread-0? 6
 Thread-0? 7
 Thread-0? 8
 Thread-0? 9
 main? 3
 main? 4
 main? 5
 main? 6
 main? 7
 main? 8
 main? 9
4.5、yield方法
   
 暫停當前正在執行的線程對象。
 yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態后馬上又被執行。yield()只能使同優先級或更高優先級的線程有執行的機會。
4.6、interrupt方法
interrupt()只是為線程設置一個中斷標記,一個線程對象被設置了中斷標記只有仍然可以運行,isAlive()返回true。實例方法isInterrupted()測試線程對象中的中斷標記,并不清除標記。靜態的interrupted()方法會測試當前執行的線程是否被中斷,并且在肯定的情況下,清除當前對象中斷標記,并返回true。
interrupt()不會中斷一個正在運行的線程.就是指線程如果正在運行的過程中, 去調用此方法是沒有任何反應的.為什么呢, 因為這個方法只是提供給被阻塞的線程, 即當線程調用了Object.wait, Thread.join, Thread.sleep三種方法之一的時候, 再調用interrupt方法, 才可以中斷剛才的阻塞而繼續去執行線程.
class ThreadDemo134 extends Thread {int count = 0;public void run() {System.out.println(getName() + " 將要運行...");while (!this.isInterrupted()) {System.out.println(getName() + " 運行中 " + count++);try {Thread.sleep(400); // 休眠400毫秒} catch (InterruptedException e) { // 退出阻塞態時將捕獲異常System.out.println(getName() + "從阻塞態中退出...");this.interrupt(); // 改變線程狀態,使循環結束}}System.out.println(getName() + " 已經終止!");}public static void main(String argv[]) throws InterruptedException {ThreadDemo134 ta = new ThreadDemo134();ta.setName("ThreadA");ta.start();Thread.sleep(2000);// 主線程休眠2000毫秒,等待其他線程執行System.out.println(ta.getName() + " 正在被中斷.....");// 2000毫秒過去后,main拿到cpu控制權。ta.interrupt(); // 中斷線程ThreadA} }運行結果:
ThreadA 將要運行...
 ThreadA 運行中 0
 ThreadA 運行中 1
 ThreadA 運行中 2
 ThreadA 運行中 3
 ThreadA 運行中 4
 ThreadA 正在被中斷.....
 ThreadA從阻塞態中退出...
 ThreadA 已經終止!
五、線程的競爭關系與線程互斥
并發執行的交互線程間存在與時間有關的錯誤 // 銀行賬戶的存取款線程設計。public class Account // 賬戶類 {private String name; // 儲戶姓名private double balance; // 賬戶余額public Account(String name) {this.name = name;this.balance = 0;}public String getName() // 返回賬戶名{return name;}public double balance() // 查看賬戶余額{return balance;}public void put(double value) // 存款操作,參數為存入金額{if (value > 0)this.balance += value; // 存款操作使余額值增加}public double get(double value) // 取款操作,參數為取款金額,返回實際取到金額{if (value > 0) {if (value <= this.balance)this.balance -= value; // 取款操作使余額值減少else // 賬戶余額不夠所取時{value = this.balance; // 取走全部余額this.balance = 0;}return value; // 返回實際取款額}return 0;} }class Save extends Thread // 存款線程類 {private Account account; // 賬戶private double value; // 存款金額public Save(Account a1, double value) {this.account = a1;this.value = value;}public void run() {double howmatch = this.account.balance();// 查看賬戶余額try {sleep(1); // 花費時間,線程執行被打斷} catch (InterruptedException e) {}this.account.put(this.value);System.out.println(this.account.getName() + "賬戶:現有" + howmatch + ", 存入"+ this.value + ", 余額" + this.account.balance());} }class Fetch extends Thread // 取款線程類 {private Account account; // 賬戶private double value; // 取款金額public Fetch(Account a1, double value) {this.account = a1;this.value = value;}public void run() {double howmatch = this.account.balance();// 查看賬戶余額try {sleep(1); // 花費時間,線程執行被打斷} catch (InterruptedException e) {}System.out.println(this.account.getName() + "賬戶:現有" + howmatch + ", 取走"+ this.account.get(this.value) + ", 余額"+ this.account.balance());}public static void main(String args[]) {Account wang = new Account("Wang");(new Save(wang, 100)).start();(new Save(wang, 200)).start();(new Fetch(wang, 300)).start();(new Save(new Account("Li"), 100)).start();}}錯誤結果:
Wang賬戶:現有0.0, 取走300.0, 余額0.0
 Wang賬戶:現有0.0, 存入100.0, 余額0.0
 Wang賬戶:現有0.0, 存入200.0, 余額0.0
 Li賬戶:現有0.0, 存入100.0, 余額100.0
5.1、線程間的競爭關系
如果兩個線程要訪問同一資源,則線程間存在資源競爭關系。一個線程通過操作系統得到該資源,另一個將得不到。這時候一個線程的執行可能影響到同其競爭資源的其他線程。在極端情況下,被阻塞的線程永遠得不到訪問權,從而不能成功終止。資源競爭會出現兩個問題:
死鎖(deadlock) :一組進程獲得了部分資源,還想獲得其他進程占用的資源,最終所有的進程陷入死鎖。
饑餓(starvation):一個進程由于其他進程優先級總是優于它而被無限期拖延。
5.2、臨界區管理
每個進程中訪問臨界資源的那段程序稱為臨界區(Critical Section)(臨界資源是一次僅允許一個進程使用的共享資源)。每次只準許一個進程進入臨界區,進入后不允許其他進程進入。不論是硬件臨界資源,還是軟件臨界資源,多個進程必須互斥地對它進行訪問。
   多個進程中涉及到同一個臨界資源的臨界區稱為相關臨界區。 
   進程進入臨界區的調度原則是: ①如果有若干進程要求進入空閑的臨界區,一次僅允許一個進程進入。②任何時候,處于臨界區內的進程不可多于一個。如已有進程進入自己的臨界區,則其它所有試圖進入臨界區的進程必須等待。③進入臨界區的進程要在有限時間內退出,以便其它進程能及時進入自己的臨界區。④如果進程不能進入自己的臨界區,則應讓出CPU,避免進程出現“忙等”現象。
5.3、Java線程互斥實現
Java采用synchronized關鍵字聲明一段程序為臨界區。synchronized有兩種用法:同步語句(代碼塊)、同步方法。
1.同步語句synchronized(對象)
???語句
2.同步方法synchronized?方法聲明
范例:使用同步代碼塊完成進程互斥
//互斥的存取款線程設計。public class SaveLock extends Thread // 帶互斥鎖的存款線程類 {private Account account; // 賬戶private double value; // 存款金額public SaveLock(Account a1, double value) {this.account = a1;this.value = value;}public void run() {synchronized (this.account) // 聲明臨界區,鎖定賬戶對象{double howmatch = this.account.balance();try {sleep(1); // 花費時間} catch (InterruptedException e) {}account.put(this.value);System.out.println(this.account.getName() + "賬戶:現有" + howmatch+ ", 存入" + this.value + ", 余額" + this.account.balance());}} }class FetchLock extends Thread // 帶互斥鎖的取款線程類 {private Account account;private double value;public FetchLock(Account a1, double value) {this.account = a1;this.value = value;}public void run() {synchronized (this.account) // 聲明臨界區,鎖定賬戶對象{double howmatch = this.account.balance();try {sleep(1); // 花費時間} catch (InterruptedException e) {}System.out.println(this.account.getName() + "賬戶:現有" + howmatch+ ", 取走" + this.account.get(this.value) + ", 余額"+ this.account.balance());}}public static void main(String args[]) {Account wang = new Account("Wang");(new SaveLock(wang, 100)).start();(new SaveLock(wang, 200)).start();(new FetchLock(wang, 300)).start();}}/** 程序運行結果如下: Wang賬戶:現有0.0, 存入100.0, 余額100.0 Wang賬戶:現有100.0, 存入200.0, 余額300.0* Wang賬戶:現有300.0, 取走300.0, 余額0.0*/除了可以使用同步代碼塊之外還可以使用同步方法完成以上的操作。
5.4、一個方法的完整定義格式
???????? [public | protected | private ][static] [final] [synchronized]
????????返回值類型方法名稱(參數列表) [throws異常1,異常2,…]{
?????????????????? [return返回值 ;]
}
六、線程間的協作關系與線程同步
6.1、線程間的協作關系
發送線程與接收線程通過緩沖區實現數據傳遞
?
?
// 發送線程與接收線程。public class Buffer // 緩沖區 {private int value; // 共享變量public void put(int i) {value = i;}public int get() {return value;} }class Sender extends Thread // 發送線程類 {private Buffer buffer; // 用于交換數據的共享變量public Sender(Buffer buffer) // 指定緩沖區{this.buffer = buffer;}public void run() {for (int i = 1; i < 6; i++) // 連續向緩沖區發送若干數據{buffer.put(i);System.out.println("Sender put : " + i);try {sleep(1);} catch (InterruptedException e) {}}} }class Receiver extends Thread // 接收線程類 {private Buffer buffer;public Receiver(Buffer buffer) // 指定緩沖區{this.buffer = buffer;}public void run() {for (int i = 1; i < 6; i++) // 連續從緩沖區接收若干數據{System.out.println("\t\t\tReceiver get : " + buffer.get());try {sleep(1);} catch (InterruptedException e) {}}}public static void main(String args[]) {Buffer buffer = new Buffer();(new Sender(buffer)).start();(new Receiver(buffer)).start();}}/** 沒有sleep(1)語句時,程序運行結果如下: Sender put : 1 Sender put : 2 Sender put : 3 Sender* put : 4 Sender put : 5* * Receiver get : 5 Receiver get : 5 Receiver get : 5 Receiver get : 5 Receiver* get : 5* * * 有sleep(1)方法時,每次運行結果不確定,交替執行,間隔輸出,一種可能的運行結果如下: Sender put : 1 Receiver get : 1* Sender put : 2 Receiver get : 2 Sender put : 3 Sender put : 4 Receiver get :* 4 Sender put : 5 Receiver get : 5 Receiver get : 5*/?6.2、線程同步
線程同步是解決協作關系的手段。線程同步是指兩個以上線程基于摸個條件來協調他們的活動。一個線程的執行依賴于另一個協作線程的消息或者信號,當一個線程沒有得到另一個線程的消息或者信號時,這需要等待。知道消息或者信號到達才能被喚醒。可理解線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,于是停下來,示意B運行;B依言執行,再將結果給A;A再繼續操作。
6.3、java 線程通信方法
Object類對線程的支持:在Object類中提供了以下的方法可以實現對線程的等待及喚醒的處理:
????????????????·等待:public final void wait() throws InterruptedException
?????????????? ?·等待:public final void wait(long timeout) throws InterruptedException
????????????????·喚醒:public final void notify(),喚醒第一個等待的線程
????????????????·喚醒:public final void notifyAll(),喚醒全部等待的線程
6.4、采用信號量和同步方法實現線程同步
//采用信號量和同步方法使發送線程與接收線程同步運行。public class BufferLock // 加互斥鎖的緩沖區 {private int value; // 共享變量private boolean isEmpty = true; // value是否為空的信號量public synchronized void put(int i) // 同步方法{while (!isEmpty) // 當value不空時,等待{try {this.wait(); // 使調用該方法的當前線程等待,即阻塞自己} catch (InterruptedException e) {}}value = i; // 當value空時,value獲得值System.out.println("Sender put : " + i);isEmpty = false; // 設置value為不空狀態notify(); // 喚醒其他等待線程}public synchronized int get() // 同步方法{while (isEmpty) // 當value空時,等待{try {this.wait();} catch (InterruptedException e) {}}isEmpty = true; // 設置value為空狀態,并返回值System.out.println("\t\t\tReceiver get : " + value);notify();return value;}public static void main(String args[]) {BufferLock buffer = new BufferLock();(new Sender(buffer)).start();(new Receiver(buffer)).start();}}class Sender extends Thread // 發送線程類 {private BufferLock buffer;public Sender(BufferLock buffer) {this.buffer = buffer;}public void run() {for (int i = 1; i < 6; i++) {buffer.put(i);}} }class Receiver extends Thread // 接收線程類 {private BufferLock buffer;public Receiver(BufferLock buffer) {this.buffer = buffer;}public void run() {for (int i = 1; i < 6; i++)buffer.get();}}運行結果:
| Sender? put : 1 | 
?
 ?
 ?
?
總結
以上是生活随笔為你收集整理的java基础----线程的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 如何禁止普通用户通过rsh登陆计算节点
- 下一篇: HelloCnblogs
