Java線程詳解(一)
程序、進程、線程的概念
程序(program):是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。
進程(process):是程序的一次執行過程,或是正在運行的一個程序。動態過程:有它自身的產生、存在和消亡的過程。
如:運行中的QQ,運行中的MP3播放器
程序是靜態的,進程是動態的
線程(thread):進程可進一步細化為線程,是一個程序內部的一條執行路徑。
若一個程序可同一時間執行多個線程,就是支持多線程的
Java中多線程的創建和使用
一、定義線程
1、繼承java.lang.Thread類。
1) 定義子類繼承Thread類。
2) 子類中重寫Thread類中的run方法。
3) 創建Thread子類對象,即創建了線程對象。
4) 調用線程對象start方法:啟動線程,調用run方法。
2、實現java.lang.Runnable接口。
1)定義子類,實現Runnable接口。
2)子類中重寫Runnable接口中的run方法。
3)通過Thread類含參構造器創建線程對象。
4)將Runnable接口的子類對象作為實際參數傳遞給
Thread類的構造方法中。
5)調用Thread類的start方法:開啟線程,調用
Runnable子類接口的run方法。
3、繼承方式和實現方式的聯系與區別
1) 區別
繼承Thread: 線程代碼存放Thread子類run方法中。
2) 實現方法的好處
避免了單繼承的局限性
多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。
二、實例化線程
1、如果是擴展java.lang.Thread類的線程,則直接new即可。
2、如果是實現了java.lang.Runnable接口的類,則用Thread的構造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
三、Thread類的有關方法
1.start():啟動線程並執行相應的run()方法
2.run():子線程要執行的代碼放入run()方法中
3.currentThread():靜態的,調取當前的線程
4.getName():獲取此線程的名字
5.setName():設置此線程的名字
6.yield():調用此方法的線程釋放當前CPU的執行權
暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程
若隊列中沒有同優先級的線程,忽略此方法
7.join():在A線程中調用B線程的join()方法,表示:當執行到此方法,A線程停止執行,直至B線程執行完畢,
當某個程序執行流中調用其他線程的 join() 方法時,調用線程將被阻塞,直到 join() 方法加入的 join 線程執行完為止
低優先級的線程也可以獲得執行
8.isAlive():判斷當前線程是否還存活
9.sleep(long l):顯式的讓當前線程睡眠l毫秒
令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重排隊。
拋出InterruptedException異常
10.線程通信:wait() notify() notifyAll()
設置線程的優先級
getPriority() :返回線程優先值
setPriority(int newPriority) :改變線程的優先級
四、例子
繼承於Thread類
//模擬火車站售票窗口,開啟三個窗口售票,總票數為100張 //存在線程的安全問題(之後例子用線程同步解決) class Window extends Thread { //靜態變量,保證票數統一 static int ticket = 100; public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "售票,票號為:" + ticket--); } else { break; } } } } public class TestWindow { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } }
實現Runnable接口
//使用實現Runnable接口的方式,售票 /* * 此程序存在線程的安全問題:打印車票時,會出現重票、錯票 */ class Window1 implements Runnable { int ticket = 100; public void run() { while (true) { if (ticket > 0) { //線程睡眠10秒,暴露重票、錯票問題 try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票號為:" + ticket--); } else { break; } } } } public class TestWindow1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
線程的調度
調度策略
時間片:
搶占式:高優先級的線程搶占CPU
Java的調度方法
同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
對高優先級,使用優先調度的搶占式策略
線程的優先級
線程默認優先級是5,Thread類中有三個常量,定義線程優先級範圍:
static int MAX_PRIORITY
線程可以具有的最高優先級。
static int MIN_PRIORITY
線程可以具有的最低優先級。
static int NORM_PRIORITY
分配給線程的默認優先級。
涉及的方法:
getPriority() :返回線程優先值
setPriority(int newPriority) :改變線程的優先級
線程創建時繼承父線程的優先級
1.線程總是存在優先級,優先級範圍在1~10之間。JVM線程調度程序是基於優先級的搶先調度機制。
在大多數情況下,當前運行的線程優先級將大於或等於線程池中任何線程的優先級。但這僅僅是大多數情況。
註意:線程的優先級仍然無法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的概率較大,優先級低的並非沒機會執行。
2.當線程池中線程都具有相同的優先級,調度程序的JVM實現自由選擇它喜歡的線程。這時候調度程序的操作有兩種可能:
一是選擇一個線程運行,直到它阻塞或者運行完成為止。
二是時間分片,為池內的每個線程提供均等的運行機會。
設置線程的優先級:線程默認的優先級是創建它的執行線程的優先級。可以通過setPriority(int newPriority)更改線程的優先級。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
3.線程優先級為1~10之間的正整數,JVM從不會改變一個線程的優先級。然而,1~10之間的值是沒有保證的。一些JVM可能不能識別10個不同的值,
而將這些優先級進行每兩個或多個合並,變成少於10個的優先級,則兩個或多個優先級的線程可能被映射為一個優先級。
例子:
//創建多線程的方式一:繼承於Thread類 class PrintNum extends Thread{ public void run(){ //子線程執行的代碼 for(int i = 1;i <= 100;i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } public PrintNum(String name){ super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum("線程1"); PrintNum p2 = new PrintNum("線程2"); //優先級高的獲取CPU執行幾率高 p1.setPriority(Thread.MAX_PRIORITY);//10 p2.setPriority(Thread.MIN_PRIORITY);//1 p1.start(); p2.start(); } }
線程的生命周期
一、線程狀態
線程的狀態轉換是線程控制的基礎。線程狀態總的可分為五大狀態:分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述如下:
1、新建狀態:線程對象已經創建,還沒有在其上調用start()方法。
2、就緒狀態:當線程有資格運行,但調度程序還沒有把它選定為運行線程時線程所處的狀態。當start()方法調用時,線程首先進入可運行狀態。在線程運行之後或者從阻塞、等待或睡眠狀態回來後,也返回到可運行狀態。
3、運行狀態:線程調度程序從可運行池中選擇一個線程作為當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一一種方式。
4、等待/阻塞/睡眠狀態:這是線程有資格運行時它所處的狀態。實際上這個三狀態組合為一種,其共同點是:線程仍舊是活的,但是當前沒有條件運行。換句話說,它是可運行的,但是如果某件事件出現,他可能返回到可運行狀態。
5、死亡態:當線程的run()方法完成時就認為它死去。這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程。線程一旦死亡,就不能復生。如果在一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。
二、阻止線程執行
1、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的線程休眠(暫停執行),以“減慢線程”。當線程睡眠時,它入睡在某個地方,在蘇醒之前不會返回到可運行狀態。當睡眠時間到期,則返回到可運行狀態。
註意:
1、線程睡眠是幫助所有線程獲得運行機會的最好方法。
2、線程睡眠到期自動蘇醒,並返回到就緒狀態,不是運行狀態。sleep()中指定的時間是線程不會運行的最短時間。因此,sleep()方法不能保證該線程睡眠到期後就開始執行。
3、sleep()是靜態方法,只能控制當前正在運行的線程。
下面給個例子:
//完成1-100之間自然數的輸出。 class PrintNum extends Thread { public void run() { // 子線程執行的代碼 for (int i = 0; i <= 100; i++) { if ((i) % 10 == 0) { System.out.println(Thread.currentThread().getName() + ":-------------" + i); } System.out.print(Thread.currentThread().getName() + ":" + i); try { Thread.sleep(1); System.out.print(" 線程睡眠1毫秒!\n"); }catch (InterruptedException e) { e.printStackTrace(); } } } public PrintNum(String name) { super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum("線程"); p1.start(); } }
結果:
線程:-------------0 線程:0 線程睡眠1毫秒! 線程:1 線程睡眠1毫秒! 線程:2 線程睡眠1毫秒! 線程:3 線程睡眠1毫秒! 線程:4 線程睡眠1毫秒! 線程:5 線程睡眠1毫秒! 線程:6 線程睡眠1毫秒! 線程:7 線程睡眠1毫秒! 線程:8 線程睡眠1毫秒! 線程:9 線程睡眠1毫秒! 線程:-------------10 線程:10 線程睡眠1毫秒! 線程:11 線程睡眠1毫秒! 線程:12 線程睡眠1毫秒! 線程:13 線程睡眠1毫秒! 線程:14 線程睡眠1毫秒! 線程:15 線程睡眠1毫秒! 線程:16 線程睡眠1毫秒! 線程:17 線程睡眠1毫秒! 線程:18 線程睡眠1毫秒! 線程:19 線程睡眠1毫秒! 線程:-------------20 線程:20 線程睡眠1毫秒! 線程:21 線程睡眠1毫秒! 線程:22 線程睡眠1毫秒! 線程:23 線程睡眠1毫秒! 線程:24 線程睡眠1毫秒! 線程:25 線程睡眠1毫秒! 線程:26 線程睡眠1毫秒! 線程:27 線程睡眠1毫秒! 線程:28 線程睡眠1毫秒! 線程:29 線程睡眠1毫秒! 線程:-------------30 線程:30 線程睡眠1毫秒! 線程:31 線程睡眠1毫秒! 線程:32 線程睡眠1毫秒! 線程:33 線程睡眠1毫秒! 線程:34 線程睡眠1毫秒! 線程:35 線程睡眠1毫秒! 線程:36 線程睡眠1毫秒! 線程:37 線程睡眠1毫秒! 線程:38 線程睡眠1毫秒! 線程:39 線程睡眠1毫秒! 線程:-------------40 線程:40 線程睡眠1毫秒! 線程:41 線程睡眠1毫秒! 線程:42 線程睡眠1毫秒! 線程:43 線程睡眠1毫秒! 線程:44 線程睡眠1毫秒! 線程:45 線程睡眠1毫秒! 線程:46 線程睡眠1毫秒! 線程:47 線程睡眠1毫秒! 線程:48 線程睡眠1毫秒! 線程:49 線程睡眠1毫秒! 線程:-------------50 線程:50 線程睡眠1毫秒! 線程:51 線程睡眠1毫秒! 線程:52 線程睡眠1毫秒! 線程:53 線程睡眠1毫秒! 線程:54 線程睡眠1毫秒! 線程:55 線程睡眠1毫秒! 線程:56 線程睡眠1毫秒! 線程:57 線程睡眠1毫秒! 線程:58 線程睡眠1毫秒! 線程:59 線程睡眠1毫秒! 線程:-------------60 線程:60 線程睡眠1毫秒! 線程:61 線程睡眠1毫秒! 線程:62 線程睡眠1毫秒! 線程:63 線程睡眠1毫秒! 線程:64 線程睡眠1毫秒! 線程:65 線程睡眠1毫秒! 線程:66 線程睡眠1毫秒! 線程:67 線程睡眠1毫秒! 線程:68 線程睡眠1毫秒! 線程:69 線程睡眠1毫秒! 線程:-------------70 線程:70 線程睡眠1毫秒! 線程:71 線程睡眠1毫秒! 線程:72 線程睡眠1毫秒! 線程:73 線程睡眠1毫秒! 線程:74 線程睡眠1毫秒! 線程:75 線程睡眠1毫秒! 線程:76 線程睡眠1毫秒! 線程:77 線程睡眠1毫秒! 線程:78 線程睡眠1毫秒! 線程:79 線程睡眠1毫秒! 線程:-------------80 線程:80 線程睡眠1毫秒! 線程:81 線程睡眠1毫秒! 線程:82 線程睡眠1毫秒! 線程:83 線程睡眠1毫秒! 線程:84 線程睡眠1毫秒! 線程:85 線程睡眠1毫秒! 線程:86 線程睡眠1毫秒! 線程:87 線程睡眠1毫秒! 線程:88 線程睡眠1毫秒! 線程:89 線程睡眠1毫秒! 線程:-------------90 線程:90 線程睡眠1毫秒! 線程:91 線程睡眠1毫秒! 線程:92 線程睡眠1毫秒! 線程:93 線程睡眠1毫秒! 線程:94 線程睡眠1毫秒! 線程:95 線程睡眠1毫秒! 線程:96 線程睡眠1毫秒! 線程:97 線程睡眠1毫秒! 線程:98 線程睡眠1毫秒! 線程:99 線程睡眠1毫秒! 線程:-------------100 線程:100 線程睡眠1毫秒!
2、線程讓步yield()
Thread.yield()方法作用是:暫停當前正在執行的線程對象,並執行其他線程。
yield()應該做的是讓當前運行線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行機會。
因此,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。但是,實際中無法保證yield()
達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中。
結論:yield()從未導致線程轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致線程從運行狀態
轉到就緒狀態,但有可能沒有效果。
3、join()方法
Thread的非靜態方法join()讓一個線程B“加入”到另外一個線程A的尾部。在A執行完畢之前,B不能工作。例如:
Thread t = new MyThread();
t.start();
t.join();
另外,join()方法還有帶超時限制的重載版本。例如t.join(5000);則讓線程等待5000毫秒,如果超過這個時間,則停止等待,變為可運行狀態。
線程的加入join()對線程棧導致的結果是線程棧發生了變化,當然這些變化都是瞬時的。
小結:
到目前位置,介紹了線程離開運行狀態的3種方法:
1、調用Thread.sleep():使當前線程睡眠至少多少毫秒(盡管它可能在指定的時間之前被中斷)。
2、調用Thread.yield():不能保障太多事情,盡管通常它會讓當前運行線程回到可運行性狀態,使得有相同優先級的線程有機會執行。
3、調用join()方法:保證當前線程停止執行,直到該線程所加入的線程完成為止。然而,如果它加入的線程沒有存活,則當前線程不需要停止。
除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運行狀態:
1、線程的run()方法完成。
2、在對象上調用wait()方法(不是在線程上調用)。
3、線程不能在對象上獲得鎖定,它正試圖運行該對象的方法代碼。
4、線程調度程序可以決定將當前運行狀態移動到可運行狀態,以便讓另一個線程獲得運行機會,而不需要任何理由。
線程的同步
如果我們創建的多個線程,存在著共享數據,那麽就有可能出現線程的安全問題:當其中一個線程操作共享數據時,還未操作完成,另外的線程就參與進來,導致對共享數據的操作出現問題。
方式一:同步代碼塊:
synchronized(同步監視器){
//操作共享數據的代碼
}
註:1.同步監視器:俗稱鎖,任何一個類的對象都可以才充當鎖。要想保證線程的安全,必須要求所有的線程共用同一把鎖!
2.使用實現Runnable接口的方式創建多線程的話,同步代碼塊中的鎖,可以考慮是this。如果使用繼承Thread類的方式,慎用this!
3.共享數據:多個線程需要共同操作的變量。 明確哪部分是操作共享數據的代碼。
//模擬火車站售票窗口,開啟三個窗口售票,總票數為100張 class Window2 implements Runnable { int ticket = 100;// 共享數據 public void run() { while (true) { synchronized (this) {//this表示當前對象,本題中即為w,是否鎖的標識 if (ticket > 0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票號為:" + ticket--); } } } } } public class TestWindow2 { public static void main(String[] args) { Window2 w = new Window2(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
方式二:同步方法:將操作共享數據的方法聲明為synchronized。
比如:public synchronized void show(){ //操作共享數據的代碼}
註:1.對於非靜態的方法而言,使用同步的話,默認鎖為:this。如果使用在繼承的方式實現多線程的話,慎用!
2.對於靜態的方法,如果使用同步,默認的鎖為:當前類本身。以單例的懶漢式為例。 Class clazz = Singleton.class
class Window4 implements Runnable { int ticket = 100;// 共享數據 public void run() { while (true) { show(); } } public synchronized void show() { if (ticket > 0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票號為:" + ticket--); } } } public class TestWindow4 { public static void main(String[] args) { Window4 w = new Window4(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
即使是線程安全類,也應該特別小心,因為操作的線程是間仍然不一定安全。
舉個形象的例子,比如一個集合是線程安全的,有兩個線程在操作同一個集合對象,當第一個線程查詢集合非空後,刪除集合中所有元素的時候。第二個線程也來執行與第一個線程相同的操作,也許在第一個線程查詢後,第二個線程也查詢出集合非空,但是當第一個執行清除後,第二個再執行刪除顯然是不對的,因為此時集合已經為空了。
看個代碼:
publicclass NameList { private List nameList = Collections.synchronizedList(new LinkedList()); publicvoid add(String name) { nameList.add(name); } public String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { returnnull; } } }
publicclass Test { publicstaticvoid main(String[] args) { final NameList nl =new NameList(); nl.add("aaa"); class NameDropperextends Thread{ publicvoid run(){ String name = nl.removeFirst(); System.out.println(name); } } Thread t1 = new NameDropper(); Thread t2 = new NameDropper(); t1.start(); t2.start(); } }
雖然集合對象
private List nameList = Collections.synchronizedList(new LinkedList());
是同步的,但是程序還不是線程安全的。
出現這種事件的原因是,上例中一個線程操作列表過程中無法阻止另外一個線程對列表的其他操作。
解決上面問題的辦法是,在操作集合對象的NameList上面做一個同步。改寫後的代碼如下:
publicclass NameList { private List nameList = Collections.synchronizedList(new LinkedList()); publicsynchronizedvoid add(String name) { nameList.add(name); } publicsynchronized String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { returnnull; } } }
這樣,當一個線程訪問其中一個同步方法時,其他線程只有等待。
線程的通信
wait() 與 notify() 和 notifyAll()
wait():令當前線程掛起並放棄CPU、同步資源,使別的線程可訪問並修改共享資源,而當前線程排隊等候再次對資源的訪問
notify():喚醒正在排隊等待同步資源的線程中優先級最高者結束等待
notifyAll ():喚醒正在排隊等待資源的所有線程結束等待.
Java.lang.Object提供的這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常
生產者消費者例子:
/** * Java線程:並發協作-生產者消費者模型 * */ publicclass Test { publicstaticvoid main(String[] args) { Godown godown = new Godown(30); Consumer c1 = new Consumer(50, godown); Consumer c2 = new Consumer(20, godown); Consumer c3 = new Consumer(30, godown); Producer p1 = new Producer(10, godown); Producer p2 = new Producer(10, godown); Producer p3 = new Producer(10, godown); Producer p4 = new Producer(10, godown); Producer p5 = new Producer(10, godown); Producer p6 = new Producer(10, godown); Producer p7 = new Producer(80, godown); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } } /** * 倉庫 */ class Godown { publicstaticfinalint max_size = 100;//最大庫存量 publicint curnum; //當前庫存量 Godown() { } Godown(int curnum) { this.curnum = curnum; } /** * 生產指定數量的產品 * * @param neednum */ public synchronized void produce(int neednum) { //測試是否需要生產 while (neednum + curnum > max_size) { System.out.println("要生產的產品數量" + neednum +"超過剩余庫存量" + (max_size - curnum) +",暫時不能執行生產任務!"); try { //當前的生產線程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //滿足生產條件,則進行生產,這裏簡單的更改當前庫存量 curnum += neednum; System.out.println("已經生產了" + neednum +"個產品,現倉儲量為" + curnum); //喚醒在此對象監視器上等待的所有線程 notifyAll(); } /** * 消費指定數量的產品 * * @param neednum */ public synchronized void consume(int neednum) { //測試是否可消費 while (curnum < neednum) { try { //當前的生產線程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //滿足消費條件,則進行消費,這裏簡單的更改當前庫存量 curnum -= neednum; System.out.println("已經消費了" + neednum +"個產品,現倉儲量為" + curnum); //喚醒在此對象監視器上等待的所有線程 notifyAll(); } } /** * 生產者 */ class Producer extends Thread { privateint neednum; //生產產品的數量 private Godown godown; //倉庫 Producer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } publicvoid run() { //生產指定數量的產品 godown.produce(neednum); } } /** * 消費者 */ class Consumer extends Thread { privateint neednum; //生產產品的數量 private Godown godown; //倉庫 Consumer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } publicvoid run() { //消費指定數量的產品 godown.consume(neednum); } }
已經生產了10個產品,現倉儲量為40
已經生產了10個產品,現倉儲量為50
已經消費了50個產品,現倉儲量為0
已經生產了80個產品,現倉儲量為80
已經消費了30個產品,現倉儲量為50
已經生產了10個產品,現倉儲量為60
已經消費了20個產品,現倉儲量為40
已經生產了10個產品,現倉儲量為50
已經生產了10個產品,現倉儲量為60
已經生產了10個產品,現倉儲量為70
Process finished with exit code 0
說明:
對於本例,要說明的是當發現不能滿足生產或者消費條件的時候,調用對象的wait方法,wait方法的作用是釋放當前線程的所獲得的鎖,並調用對象的notifyAll()方法,通知(喚醒)該對象上其他等待線程,使得其繼續執行。這樣,整個生產者、消費者線程得以正確的協作執行。
notifyAll() 方法,起到的是一個通知作用,不釋放鎖,也不獲取鎖。只是告訴該對象上等待的線程“可以競爭執行了,都醒來去執行吧”。
本例僅僅是生產者消費者模型中最簡單的一種表示,本例中,如果消費者消費的倉儲量達不到滿足,而又沒有生產者,則程序會一直處於等待狀態,這當然是不對的。實際上可以將此例進行修改,修改為,根據消費驅動生產,同時生產兼顧倉庫,如果倉不滿就生產,並對每次最大消費量做個限制,這樣就不存在此問題了,當然這樣的例子更復雜,更難以說明這樣一個簡單模型。
總結:釋放鎖::wait();
不釋放鎖:sleep() yield() suspend() (過時,可能導致死鎖)
線程死鎖
不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
死鎖是我們在使用同步時,需要避免的問題!
//死鎖的問題:處理線程同步時容易出現。 //不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖 //寫代碼時,要避免死鎖! public class TestDeadLock { static StringBuffer sb1 = new StringBuffer(); static StringBuffer sb2 = new StringBuffer(); public static void main(String[] args) { new Thread() { public void run() { synchronized (sb1) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } sb1.append("A"); synchronized (sb2) { sb2.append("B"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); new Thread() { public void run() { synchronized (sb2) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } sb1.append("C"); synchronized (sb1) { sb2.append("D"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); } }
線程的調度-守護線程
守護線程與普通線程寫法上基本麽啥區別,調用線程對象的方法setDaemon(true),則可以將其設置為守護線程。
守護線程使用的情況較少,但並非無用,舉例來說,JVM的垃圾回收、內存管理等線程都是守護線程。還有就是在做數據庫應用時候,使用的數據庫連接池,連接池本身也包含著很多後臺線程,監控連接個數、超時時間、狀態等等。
setDaemon方法的詳細說明:
publicfinalvoid setDaemon(boolean on)將該線程標記為守護線程或用戶線程。當正在運行的線程都是守護線程時,Java虛擬機退出。
該方法必須在啟動線程前調用。
該方法首先調用該線程的 checkAccess方法,且不帶任何參數。這可能拋出 SecurityException(在當前線程中)。
參數:
on - 如果為true,則將該線程標記為守護線程。
拋出:
IllegalThreadStateException - 如果該線程處於活動狀態。
SecurityException - 如果當前線程無法修改該線程。
另請參見:
isDaemon(), checkAccess()
/** * Java線程:線程的調度-守護線程 * * @author leizhimin 2009-11-4 9:02:40 */ public class Test { public static void main(String[] args) { Thread t1 = new MyCommon(); Thread t2 = new Thread(new MyDaemon()); t2.setDaemon(true);//設置為守護線程 t2.start(); t1.start(); } } class MyCommon extends Thread { publicvoid run() { for (int i = 0; i < 5; i++) { System.out.println("線程1第" + i + "次執行!"); try { Thread.sleep(7); } catch (InterruptedException e){ e.printStackTrace(); } } } } class MyDaemon implements Runnable { publicvoid run() { for (long i = 0; i < 9999999L; i++) { System.out.println("後臺線程第" + i +"次執行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
後臺線程第0次執行!
線程1第0次執行!
線程1第1次執行!
後臺線程第1次執行!
後臺線程第2次執行!
線程1第2次執行!
線程1第3次執行!
後臺線程第3次執行!
線程1第4次執行!
後臺線程第4次執行!
後臺線程第5次執行!
後臺線程第6次執行!
後臺線程第7次執行!
Process finished with exit code 0
從上面的執行結果可以看出:
前臺線程是保證執行完畢的,後臺線程還沒有執行完畢就退出了。
實際上:JRE判斷程序是否執行結束的標準是所有的前臺執線程行完畢了,而不管後臺線程的狀態,因此,在使用後臺縣城時候一定要註意這個問題。
Java線程:volatile關鍵字
Java 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile變量。這兩種機制的提出都是為了實現代碼線程的安全性。其中 Volatile變量的同步性較差(但有時它更簡單並且開銷更低),而且其使用也更容易出錯。
談及到volatile關鍵字,不得不提的一篇文章是:《Java理論與實踐:正確使用 Volatile 變量》,這篇文章對volatile關鍵字的用法做了相當精辟的闡述。
之所以要單獨提出volatile這個不常用的關鍵字原因是這個關鍵字在高性能的多線程程序中也有很重要的用途,只是這個關鍵字用不好會出很多問題。
首先考慮一個問題,為什麽變量需要volatile來修飾呢?
要搞清楚這個問題,首先應該明白計算機內部都做什麽了。比如做了一個i++操作,計算機內部做了三次處理:讀取-修改-寫入。
同樣,對於一個long型數據,做了個賦值操作,在32系統下需要經過兩步才能完成,先修改低32位,然後修改高32位。
假想一下,當將以上的操作放到一個多線程環境下操作時候,有可能出現的問題,是這些步驟執行了一部分,而另外一個線程就已經引用了變量值,這樣就導致了讀取臟數據的問題。
通過這個設想,就不難理解volatile關鍵字了。
volatile可以用在任何變量前面,但不能用於final變量前面,因為final型的變量是禁止修改的。也不存在線程安全的問題。
本文出自 “ciyo技術分享” 博客,請務必保留此出處http://ciyorecord.blog.51cto.com/6010867/1939492
Java線程詳解(一)