本文地址:http://m.survivalescaperooms.com/archimedes/p/java-study-note14.html,轉載請注明源地址。
多線程編程基礎多進程
一個獨立程序的每一次運行稱為一個進程,例如:用字處理軟件編輯文稿時,同時打開mp3播放程序聽音樂,這兩個獨立的程序在同時運行,稱為兩個進程
進程要占用相當一部分處理器時間和內存資源
進程具有獨立的內存空間
通信很不方便,編程模型比較復雜
多線程
一個程序中多段代碼同時并發執行,稱為多線程,線程比進程開銷小,協作和數據交換容易
Java是第一個支持內置線程操作的主流編程語言,多數程序設計語言支持多線程要借助于操作系統“原語(PRimitives)”
Thread類
直接繼承了Object類,并實現了Runnable接口。位于java.lang包中封裝了線程對象需要的屬性和方法
繼承Thread類——創建多線程的方法之一,類派生一個子類,并創建子類的對象,子類應該重寫Thread類的run方法,寫入需要在新線程中執行的語句段。調用start方法來啟動新線程,自動進入run方法。
(實例1)在新線程中完成計算某個整數的階乘
class FactorialThread extends Thread { private int num; public FactorialThread(int num) { this.num = num; } public void run() { int i = num; int result = 1; System.out.println("new thread started"); while(i > 0) { result = result * i; i--; } System.out.println("The factorial of " + num + " is " + result); System.out.println("new thread ends"); }}public class javatest { public static void main(String args[]) { System.out.println("main thread start"); FactorialThread thread = new FactorialThread(10); thread.start(); System.out.println("main thread ends"); }}運行結果:
main thread startmain thread endsnew thread startedThe factorial of 10 is 3628800new thread ends
結果說明:
main線程已經執行完后,新線程才執行完。main函數調用thread.start()方法啟動新線程后并不等待其run方法返回就繼續運行,thread.run函數在一邊獨自運行,不影響原來的main函數的運行
如果啟動新線程后希望主線程多持續一會再結束,可在start語句后加上讓當前線程(這里當然是main)休息1毫秒的語句:
try { Thread.sleep(1); } catch(Exception e){};
常用API方法:
| 名稱 | 說明 |
| public Thread() | 構造一個新的線程對象,默認名為Thread-n,n是從0開始遞增的整數 |
| public Thread(Runnable target) | 構造一個新的線程對象,以一個實現Runnable接口的類的對象為參數。默認名為Thread-n,n是從0開始遞增的整數 |
| public Thread(String name) | 構造一個新的線程對象,并同時指定線程名 |
| public static Thread currentThread() | 返回當前正在運行的線程對象 |
| public static void yield() | 使當前線程對象暫停,允許別的線程開始運行 |
| public static void sleep(long millis) | 使當前線程暫停運行指定毫秒數,但此線程并不失去已獲得的鎖旗標。 |
| public void start() | 啟動線程,JVM將調用此線程的run方法,結果是將同時運行兩個線程,當前線程和執行run方法的線程 |
| public void run() | Thread的子類應該重寫此方法,內容應為該線程應執行的任務。 |
| public final void stop() | 停止線程運行,釋放該線程占用的對象鎖旗標。 |
| public void interrupt() | 中斷此線程 |
| public final void join() | 如果此前啟動了線程A,調用join方法將等待線程A死亡才能繼續執行當前線程 |
| public final void join(long millis) | 如果此前啟動了線程A,調用join方法將等待指定毫秒數或線程A死亡才能繼續執行當前線程 |
| public final void setPriority(int newPriority) | 設置線程優先級 |
| public final void setDaemon(Boolean on) | 設置是否為后臺線程,如果當前運行線程均為后臺線程則JVM停止運行。這個方法必須在start()方法前使用 |
| public void setName(String name) | 更改本線程的名稱為指定參數 |
| public final boolean isAlive() | 測試線程是否處于活動狀態,如果線程被啟動并且沒有死亡則返回true |
| public final void checkaccess() | 判斷當前線程是否有權力修改調用此方法的線程 |
(實例2):創建3個新線程,每個線程睡眠一段時間(0~6秒),然后結束。
class TestThread extends Thread { private int sleeptime; public TestThread(String name) { super(name); sleeptime = (int)(Math.random() * 6000); } public void run() { try { System.out.println(getName() + "going to sleep for " + sleeptime); Thread.sleep(sleeptime); } catch (InterruptedException ex) { } System.out.println(getName() + " finished"); }}public class javatest { public static void main(String args[]) { TestThread thr1 = new TestThread("thread1"); TestThread thr2 = new TestThread("thread2"); TestThread thr3 = new TestThread("thread3"); System.out.println("staring threads"); thr1.start(); thr2.start(); thr3.start(); System.out.println("Thread started, main ends"); }}運行結果:
staring threadsthread1going to sleep for 2925Thread started, main endsthread3going to sleep for 1222thread2going to sleep for 5007thread3 finishedthread1 finishedthread2 finished
Runnable接口
Thread類實現了Runnable接口,只有一個run()方法,更便于多個線程共享資源。Java不支持多繼承,如果已經繼承了某個基類,便需要實現Runnable接口來生成多線程以實現runnable的對象為參數建立新的線程,start方法啟動線程就會運行run()方法
(實例3)使用Runnable接口實現實例1的功能:
class FactorialThread implements Runnable { private int num; public FactorialThread(int num) { this.num = num; } public void run() { int i = num; int result = 1; while(i > 0) { result = result * i; i--; } System.out.println("The factorial of " + num + " is " + result); System.out.println("new thread ends"); }}public class javatest { public static void main(String args[]) { System.out.println("main thread starts"); FactorialThread t = new FactorialThread(10); new Thread(t).start(); System.out.println("new thread started main thread ends"); }}(實例4)使用Runnable接口實現實例2的功能:
class TestThread implements Runnable { private int sleepTime; public TestThread() { sleepTime = (int)(Math.random() * 6000); } public void run() { try { System.out.println(Thread.currentThread().getName() + " going to sleep for " + sleepTime); Thread.sleep(sleepTime); } catch(InterruptedException ex) { }; System.out.println(Thread.currentThread().getName() + " finished"); }}public class javatest { public static void main(String args[]) { TestThread thread1 = new TestThread(); TestThread thread2 = new TestThread(); TestThread thread3 = new TestThread(); System.out.println("Starting threads"); new Thread(thread1, "Thread1").start(); new Thread(thread2, "Thread2").start(); new Thread(thread3, "Thread3").start(); System.out.println("Threads started, main ends"); }}線程間的數據共享
用同一個實現了Runnable接口的對象作為參數創建多個線程
多個線程共享同一對象中的相同的數據
修改實例4,只用一個Runnable類型的對象為參數創建3個新線程:
class TestThread implements Runnable { private int sleepTime; public TestThread() { sleepTime = (int)(Math.random() * 6000); } public void run() { try { System.out.println(Thread.currentThread().getName() + " going to sleep for " + sleepTime); Thread.sleep(sleepTime); } catch(InterruptedException ex) { }; System.out.println(Thread.currentThread().getName() + " finished"); }}public class javatest { public static void main(String args[]) { TestThread threadobj = new TestThread(); System.out.println("Starting threads"); new Thread(threadobj, "Thread1").start(); new Thread(threadobj, "Thread2").start(); new Thread(threadobj, "Thread3").start(); System.out.println("Threads started, main ends"); }}說明:因為是用一個Runnable類型對象創建的3個新線程,這三個線程就共享了這個對象的私有成員sleepTime,在本次運行中,三個線程都休眠了966毫秒
獨立的同時運行的線程有時需要共享一些數據并且考慮到彼此的狀態和動作
舉個例子:用三個線程模擬三個售票口,總共出售200張票
class SellTicks implements Runnable { private int tickets = 200; public void run() { while(tickets > 0) { System.out.println(Thread.currentThread().getName() + " is selling ticket " + tickets--); } }}public class javatest { public static void main(String args[]) { SellTicks t = new SellTicks(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); }}說明:
在這個例子中,創建了3個線程,每個線程調用的是同一個SellTickets對象中的run()方法,訪問的是同一個對象中的變量(tickets)
如果是通過創建Thread類的子類來模擬售票過程,再創建3個新線程,則每個線程都會有各自的方法和變量,雖然方法是相同的,但變量卻是各有200張票,因而結果將會是各賣出200張票,和原意就不符了
多線程的同步控制
有時線程之間彼此不獨立、需要同步
線程間的互斥
同時運行的幾個線程需要共享一個(些)數據
共享的數據,在某一時刻只允許一個線程對其進行操作
“生產者/消費者” 問題
假設有一個線程負責往數據區寫數據,另一個線程從同一數據區中讀數據,兩個線程可以并行執行,如果數據區已滿,生產者要等消費者取走一些數據后才能再寫。當數據區空時,消費者要等生產者寫入一些數據后再取
舉個例子:
用兩個線程模擬存票、售票過程
假定開始售票處并沒有票,一個線程往里存票,另外一個線程則往出賣票,我們新建一個票類對象,讓存票和售票線程都訪問它。本例采用兩個線程共享同一個數據對象來實現對同一份數據的操作
class Tickets { int number = 0; //票號 int size; //總票數 boolean available = false; //表示目前是否有票可售 public Tickets(int size) { //構造函數,傳入總票參數 this.size = size; }}class Producer extends Thread { Tickets t = null; public Producer(Tickets t) { this.t = t; } public void run() { while(t.number < t.size) { System.out.println("Producer puts ticket" + (++t.number)); t.available = true; } }}class Consumer extends Thread { Tickets t = null; int i = 0; public Consumer(Tickets t) { this.t = t; } public void run() { while(i < t.size) { if(t.available == true && i <= t.number) System.out.println("Consumer buys ticket" + (++i)); if(i == t.number) t.available = false; } }}public class TicketSell { public static void main(String[] args) { Tickets t = new Tickets(10); new Consumer(t).start(); new Producer(t).start(); }}運行結果:
Producer puts ticket1Producer puts ticket2Consumer buys ticket1Producer puts ticket3Consumer buys ticket2Producer puts ticket4Producer puts ticket5Producer puts ticket6Producer puts ticket7Producer puts ticket8Producer puts ticket9Producer puts ticket10Consumer buys ticket3Consumer buys ticket4Consumer buys ticket5Consumer buys ticket6Consumer buys ticket7Consumer buys ticket8Consumer buys ticket9Consumer buys ticket10
通過讓兩個線程操縱同一個票類對象,實現了數據共享的目的,
線程同步(Synchronization)
互斥:許多線程在同一個共享數據上操作而互不干擾,同一時刻只能有一個線程訪問該共享數據。因此有些方法或程序段在同一時刻只能被一個線程執行,稱之為監視區
協作:多個線程可以有條件地同時操作共享數據。執行監視區代碼的線程在條件滿足的情況下可以允許其它線程進入監視區
synchronized ——線程同步關鍵字
用于指定需要同步的代碼段或方法,也就是監視區
可實現與一個鎖旗標的交互。例如:
synchronized(對象){ 代碼段 }
synchronized的功能是:首先判斷對象的鎖旗標是否在,如果在就獲得鎖旗標,然后就可以執行緊隨其后的代碼段;如果對象的鎖旗標不在(已被其他線程拿走),就進入等待狀態,直到獲得鎖旗標
當被synchronized限定的代碼段執行完,就釋放鎖旗標
互斥:存票線程和售票線程應保持互斥關系。即售票線程執行時不進入存票線程、存票線程執行時不進入售票線程
Java 使用監視器機制
–每個對象只有一個“鎖旗標” ,利用多線程對“鎖旗標”的爭奪實現線程間的互斥
–當線程A獲得了一個對象的鎖旗標后,線程B必須等待線程A完成規定的操作、并釋放出鎖旗標后,才能獲得該對象的鎖旗標,并執行線程B中的操作
將需要互斥的語句段放入synchronized(object){}語句框中,且兩處的object是相同的
修改上面的代碼:
class Producer extends Thread { Tickets t = null; public Producer(Tickets t) { this.t = t; } public void run() { while(t.number < t.size) { synchronized (t) { //申請對象t的鎖旗標 System.out.println("Producer puts ticket" + (++t.number)); t.available = true; } //釋放對象t的鎖旗標 } }}class Consumer extends Thread { Tickets t = null; int i = 0; public Consumer(Tickets t) { this.t = t; } public void run() { while(i < t.size) { synchronized (t) { ////申請對象t的鎖旗標 if(t.available == true && i <= t.number) System.out.println("Consumer buys ticket" + (++i)); if(i == t.number) t.available = false; } } //釋放對象t的鎖旗標 }}運行結果:
Producer puts ticket1Producer puts ticket2Producer puts ticket3Producer puts ticket4Producer puts ticket5Producer puts ticket6Producer puts ticket7Producer puts ticket8Producer puts ticket9Producer puts ticket10Consumer buys ticket1Consumer buys ticket2Consumer buys ticket3Consumer buys ticket4Consumer buys ticket5Consumer buys ticket6Consumer buys ticket7Consumer buys ticket8Consumer buys ticket9Consumer buys ticket10
說明:
1、存票程序段和售票程序段為獲得同一對象的鎖旗標而實現互斥操作
2、當線程執行到synchronized的時候,檢查傳入的實參對象,并申請得到該對象的鎖旗標。如果得不到,那么線程就被放到一個與該對象鎖旗標相對應的等待線程池中。直到該對象的鎖旗標被歸還,池中的等待線程才能重新去獲得鎖旗標,然后繼續執行下去
3、除了可以對指定的代碼段進行同步控制之外,還可以定義整個方法在同步控制下執行,只要在方法定義前加上synchronized關鍵字即可
把上面的程序再修改一下:
class Tickets { int number = 0; //票號 int size; //總票數 int i = 0; //售票序號 boolean available = false; //表示是否有票可售 public Tickets(int size) { //構造函數,傳入總票參數 this.size = size; } public synchronized void put() { //同步方法,實現存票的功能 System.out.println("Producer puts ticket" + (++number)); available = true; } public synchronized void sell() { //同步方法,實現售票的功能 if(available == true && i <= number) System.out.println("Consumer buys ticket" + (++i)); if(i == number) available = false; }}class Producer extends Thread { Tickets t = null; public Producer(Tickets t) { this.t = t; } public void run() { //如果存票數小于限定總量,則不斷存入票 while(t.number < t.size) t.put(); }}class Consumer extends Thread { Tickets t = null; int i = 0; public Consumer(Tickets t) { this.t = t; } public void run() { //如果售票數小于限定總量,則不斷售票 while(i < t.size) t.sell(); }}線程之間的通信
為了更有效地協調不同線程的工作,需要在線程間建立溝通渠道,通過線程間的“對話”來解決線程間的同步問題
java.lang.Object 類的一些方法為線程間的通訊提供了有效手段
wait() 如果當前狀態不適合本線程執行,正在執行同步代碼(synchronized)的某個線程A調用該方法(在對象x上),該線程暫停執行而進入對象x的等待池,并釋放已獲得的對象x的鎖旗標。線程A要一直等到其他線程在對象x上調用notify或notifyAll方法,才能夠在重新獲得對象x的鎖旗標后繼續執行(從wait語句后繼續執行)
notify()和notifyAll()方法:
notify() 隨機喚醒一個等待的線程,本線程繼續執行
線程被喚醒以后,還要等發出喚醒消息者釋放監視器,這期間關鍵數據仍可能被改變
被喚醒的線程開始執行時,一定要判斷當前狀態是否適合自己運行
notifyAll() 喚醒所有等待的線程,本線程繼續執行
修改上面的例子,使每存入一張票,就售一張票,售出后,再存入。 代碼如下:
package javatest;import com.sun.org.apache.xalan.internal.xslt.Process;class Tickets { int number = 0; //票號 int size; //總票數 int i = 0; //售票序號 boolean available = false; //表示是否有票可售 public Tickets(int size) { //構造函數,傳入總票參數 this.size = size; } public synchronized void put() { if(available) { //如果還有存票待售,則存票線程等待 try { wait(); } catch (Exception e) { // TODO: handle exception } } System.out.println("Producer puts ticket" + (++number)); available = true; notify(); //存票后喚醒售票線程開始售票 } public synchronized void sell() { if(!available) { //如果沒有存票,則售票線程等待 try { wait(); } catch(Exception e) { } } System.out.println("Consumer buys ticket" + (number)); available = false; notify(); if(number == size) number = size + 1; //在售完最后一張票后, //設置一個結束標志,number>size表示售票結束 }}class Producer extends Thread { Tickets t = null; public Producer(Tickets t) { this.t = t; } public void run() { //如果存票數小于限定總量,則不斷存入票 while(t.number < t.size) t.put(); }}class Consumer extends Thread { Tickets t = null; int i = 0; public Consumer(Tickets t) { this.t = t; } public void run() { //如果售票數小于限定總量,則不斷售票 while(i < t.size) t.sell(); }}public class TicketSell { public static void main(String[] args) { Tickets t = new Tickets(10); new Consumer(t).start(); new Producer(t).start(); }}運行結果:
Producer puts ticket1Consumer buys ticket1Producer puts ticket2Consumer buys ticket2Producer puts ticket3Consumer buys ticket3Producer puts ticket4Consumer buys ticket4Producer puts ticket5Consumer buys ticket5Producer puts ticket6Consumer buys ticket6Producer puts ticket7Consumer buys ticket7Producer puts ticket8Consumer buys ticket8Producer puts ticket9Consumer buys ticket9Producer puts ticket10Consumer buys ticket10
程序說明:
當Consumer線程售出票后,available值變為false,當Producer線程放入票后,available值變為true
只有available為true時,Consumer線程才能售票,否則就必須等待Producer線程放入新的票后的通知
只有available為false時,Producer線程才能放票,否則必須等待Consumer線程售出票后的通知
后臺線程
也叫守護線程,通常是為了輔助其它線程而運行的線程
它不妨礙程序終止
一個進程中只要還有一個前臺線程在運行,這個進程就不會結束;如果一個進程中的所有前臺線程都已經結束,那么無論是否還有未結束的后臺線程,這個進程都會結束
“垃圾回收”便是一個后臺線程
如果對某個線程對象在啟動(調用start方法)之前調用了setDaemon(true)方法,這個線程就變成了后臺線程
您還可能感興趣:
java學習筆記系列:
java學習筆記13--反射機制與動態代理
java學習筆記12--異常處理
java學習筆記11--集合總結
java學習筆記10--泛型總結
java學習筆記9--內部類總結
java學習筆記8--接口總結
java學習筆記7--抽象類與抽象方法
java學習筆記6--類的繼承、Object類
java學習筆記5--類的方法
java學習筆記4--對象的初始化與回收
java學習筆記3--類與對象的基礎
java學習筆記2--數據類型、數組
java學習筆記1--開發環境平臺總結
新聞熱點
疑難解答