概述
線程的狀態(tài)
Java 中線程中狀態(tài)可分為五種:New(新建狀態(tài))容贝,Runnable(就緒狀態(tài)),Running(運(yùn)行狀態(tài))季研,Blocked(阻塞狀態(tài))座每,Dead(死亡狀態(tài))
New:新建狀態(tài),當(dāng)線程創(chuàng)建完成時(shí)為新建狀態(tài)高诺,即new Thread(...)碌识,還沒有調(diào)用start方法時(shí),線程處于新建狀態(tài)虱而。
Runnable:就緒狀態(tài)筏餐,當(dāng)調(diào)用線程的的start方法后,線程進(jìn)入就緒狀態(tài)薛窥,等待CPU資源胖烛。處于就緒狀態(tài)的線程由Java運(yùn)行時(shí)系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度眼姐。
Running:運(yùn)行狀態(tài),就緒狀態(tài)的線程獲取到CPU執(zhí)行權(quán)以后進(jìn)入運(yùn)行狀態(tài)佩番,開始執(zhí)行run方法众旗。
Blocked:阻塞狀態(tài),線程沒有執(zhí)行完趟畏,由于某種原因(如贡歧,I/O操作等)讓出CPU執(zhí)行權(quán),自身進(jìn)入阻塞狀態(tài)赋秀。
Dead:死亡狀態(tài)利朵,線程執(zhí)行完成或者執(zhí)行過程中出現(xiàn)異常,線程就會(huì)進(jìn)入死亡狀態(tài)猎莲。
這五種狀態(tài)之間的轉(zhuǎn)換關(guān)系如下圖所示
Java中是如何實(shí)現(xiàn)這幾種狀態(tài)的轉(zhuǎn)換的
通過 wait/notify/notifyAll 方法的使用
這三個(gè)方法都是在 Object 對(duì)象中定義的绍弟,本篇詳細(xì)講解
通過 sleep/yield/join 方法的使用
這三個(gè)方法都是在 Thread 對(duì)象中定義的,這里不做講解著洼,具體可看 深入理解Thread類 這篇文章
各個(gè)方法解析
以下是 Object 類的源碼
public class Object {
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException {
wait(0);
}
}
wait樟遣、notify和notifyAll方法是Object類的final native方法。所以這些方法不能被子類重寫身笤,Object類是所有類的超類豹悬,因此在程序中有以下三種形式調(diào)用wait等方法。
wait();//方式1:
this.wait();//方式2:
super.wait();//方式3
void wait()
導(dǎo)致線程進(jìn)入等待狀態(tài)液荸,直到它被其他線程通過notify()或者notifyAll喚醒瞻佛。該方法只能在同步方法或同步塊中調(diào)用。如果當(dāng)前線程不是鎖的持有者娇钱,該方法拋出一個(gè)IllegalMonitorStateException
異常伤柄。
void wait(long millis) 和 void wait(long millis, int nanos)
導(dǎo)致線程進(jìn)入等待狀態(tài)直到它被通知或者經(jīng)過指定的時(shí)間。這些方法只能在同步方法或同步塊中調(diào)用忍弛。如果當(dāng)前線程不是鎖的持有者响迂,該方法拋出一個(gè)IllegalMonitorStateException
異常。
void notifyAll()
解除所有那些在該對(duì)象上調(diào)用wait方法的線程的阻塞狀態(tài)细疚。該方法只能在同步方法或同步塊內(nèi)部調(diào)用。如果當(dāng)前線程不是鎖的持有者川梅,該方法拋出一個(gè)IllegalMonitorStateException
異常疯兼。
void notify()
隨機(jī)選擇一個(gè)在該對(duì)象上調(diào)用wait方法的線程,解除其阻塞狀態(tài)贫途。該方法只能在同步方法或同步塊內(nèi)部調(diào)用吧彪。如果當(dāng)前線程不是鎖的持有者,該方法拋出一個(gè)IllegalMonitorStateException
異常丢早。
Object.wait()和Object.notify()和Object.notifyall()必須寫在synchronized方法內(nèi)部或者synchronized塊內(nèi)部姨裸,這是因?yàn)椋?strong>這幾個(gè)方法要求當(dāng)前正在運(yùn)行object.wait()方法的線程擁有object的對(duì)象鎖秧倾。即使你確實(shí)知道當(dāng)前上下文線程確實(shí)擁有了對(duì)象鎖,也不能將object.wait()這樣的語(yǔ)句寫在當(dāng)前上下文中傀缩。
從這三個(gè)方法的文字描述可以知道以下幾點(diǎn)信息:
1)wait()那先、notify()和notifyAll()方法是本地方法,并且為final方法赡艰,無法被重寫售淡。
2)調(diào)用某個(gè)對(duì)象的wait()方法能讓當(dāng)前線程阻塞,并且當(dāng)前線程必須擁有此對(duì)象的monitor(即鎖)
3)調(diào)用某個(gè)對(duì)象的notify()方法能夠喚醒一個(gè)正在等待這個(gè)對(duì)象的monitor的線程慷垮,如果有多個(gè)線程都在等待這個(gè)對(duì)象的monitor揖闸,則只能喚醒其中一個(gè)線程;
4)調(diào)用notifyAll()方法能夠喚醒所有正在等待這個(gè)對(duì)象的monitor的線程料身;
上面已經(jīng)提到汤纸,如果調(diào)用某個(gè)對(duì)象的wait()方法,當(dāng)前線程必須擁有這個(gè)對(duì)象的monitor(即鎖)芹血,因此調(diào)用wait()方法必須在同步塊或者同步方法中進(jìn)行(synchronized塊或者synchronized方法)蹲嚣。
調(diào)用某個(gè)對(duì)象的wait()方法,相當(dāng)于讓當(dāng)前線程交出此對(duì)象的monitor祟牲,然后進(jìn)入等待狀態(tài)隙畜,等待后續(xù)再次獲得此對(duì)象的鎖(Thread類中的sleep方法使當(dāng)前線程暫停執(zhí)行一段時(shí)間,從而讓其他線程有機(jī)會(huì)繼續(xù)執(zhí)行说贝,但它并不釋放對(duì)象鎖)议惰;
notify()方法能夠喚醒一個(gè)正在等待該對(duì)象的monitor的線程,當(dāng)有多個(gè)線程都在等待該對(duì)象的monitor的話乡恕,則只能喚醒其中一個(gè)線程言询,具體喚醒哪個(gè)線程則不得而知。
同樣地傲宜,調(diào)用某個(gè)對(duì)象的notify()方法运杭,當(dāng)前線程也必須擁有這個(gè)對(duì)象的monitor,因此調(diào)用notify()方法必須在同步塊或者同步方法中進(jìn)行(synchronized塊或者synchronized方法)函卒。
nofityAll()方法能夠喚醒所有正在等待該對(duì)象的monitor的線程辆憔,這一點(diǎn)與notify()方法是不同的。
這里要注意一點(diǎn):notify()和notifyAll()方法只是喚醒等待該對(duì)象的monitor的線程报嵌,并不決定哪個(gè)線程能夠獲取到monitor虱咧。
舉個(gè)簡(jiǎn)單的例子:假如有三個(gè)線程Thread1、Thread2和Thread3都在等待對(duì)象objectA的monitor锚国,此時(shí)Thread4擁有對(duì)象objectA的monitor腕巡,當(dāng)在Thread4中調(diào)用objectA.notify()方法之后,Thread1血筑、Thread2和Thread3只有一個(gè)能被喚醒绘沉。注意煎楣,被喚醒不等于立刻就獲取了objectA的monitor。假若在Thread4中調(diào)用objectA.notifyAll()方法车伞,則Thread1择懂、Thread2和Thread3三個(gè)線程都會(huì)被喚醒,至于哪個(gè)線程接下來能夠獲取到objectA的monitor就具體依賴于操作系統(tǒng)的調(diào)度了帖世。
上面尤其要注意一點(diǎn)休蟹,一個(gè)線程被喚醒不代表立即獲取了對(duì)象的monitor,只有等調(diào)用完notify()或者notifyAll()并退出synchronized塊日矫,釋放對(duì)象鎖后赂弓,其余線程才可獲得鎖執(zhí)行。
為什么wait()和notify()屬于Object類
wait()和notify()是Java給我們提供線程之間通信的API哪轿,既然是線程的東西盈魁,那什么是在Object類上定義,而不是在Thread類上定義呢窃诉?
其實(shí)這個(gè)問題很簡(jiǎn)單杨耙,由于每個(gè)對(duì)象都擁有monitor(即鎖),所以讓當(dāng)前線程等待某個(gè)對(duì)象的鎖飘痛,當(dāng)然應(yīng)該通過這個(gè)對(duì)象來操作了珊膜。而不是用當(dāng)前線程來操作,因?yàn)楫?dāng)前線程可能會(huì)等待多個(gè)線程的鎖宣脉,如果通過線程來操作车柠,就非常復(fù)雜了。
Java 線程wait()之后一定要notify()才能喚醒嗎塑猖?
不一定竹祷!
線程正常結(jié)束后,會(huì)使以這個(gè)線程對(duì)象運(yùn)行的wait()等待羊苟,退出等待狀態(tài)塑陵!而如果在運(yùn)行wait()之前,線程已經(jīng)結(jié)束了蜡励,則這個(gè)wait就沒有程序喚醒了
實(shí)際上令花,Thread源碼里面的join()方法也是使用這種機(jī)制:
As a thread terminates the {@code this.notifyAll} method is invoked.
源碼里的join()方法,實(shí)際上就是運(yùn)行的 wait(). 需要運(yùn)行join的線程運(yùn)行join方法巍虫,實(shí)際上是在此線程上調(diào)用了需要加入的線程對(duì)象的wait()方法彭则,加入的線程運(yùn)行完后,會(huì)自動(dòng)調(diào)用 notifyAll 方法占遥,被 join 的線程自然能夠繼續(xù)執(zhí)行了。
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
線程對(duì)象的wait()方法運(yùn)行后输瓜,可以不用其notify()方法退出瓦胎,會(huì)在線程結(jié)束后芬萍,自動(dòng)退出。
Condition
Condition是在java 1.5中才出現(xiàn)的搔啊,它用來替代傳統(tǒng)的Object的wait()柬祠、notify()實(shí)現(xiàn)線程間的協(xié)作,相比使用Object的wait()负芋、notify()漫蛔,使用Condition1的await()、signal()這種方式實(shí)現(xiàn)線程間協(xié)作更加安全和高效旧蛾。因此通常來說比較推薦使用Condition莽龟,在阻塞隊(duì)列那一篇博文中就講述到了,阻塞隊(duì)列實(shí)際上是使用了Condition來模擬線程間協(xié)作锨天。
- Condition是個(gè)接口毯盈,基本的方法就是await()和signal()方法;
- Condition依賴于Lock接口病袄,生成一個(gè)Condition的基本代碼是lock.newCondition()
- 調(diào)用Condition的await()和signal()方法搂赋,都必須在lock保護(hù)之內(nèi),就是說必須在lock.lock()和lock.unlock之間才可以使用
Conditon中的await()對(duì)應(yīng)Object的wait()益缠;
Condition中的signal()對(duì)應(yīng)Object的notify()脑奠;
Condition中的signalAll()對(duì)應(yīng)Object的notifyAll()。
生產(chǎn)者-消費(fèi)者模型的實(shí)現(xiàn)
1.使用Object的wait()和notify()實(shí)現(xiàn)
public class Test {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
while(true){
synchronized (queue) {
while(queue.size() == 0){
try {
System.out.println("隊(duì)列空幅慌,等待數(shù)據(jù)");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.poll(); //每次移走隊(duì)首元素
queue.notify();
System.out.println("從隊(duì)列取走一個(gè)元素宋欺,隊(duì)列剩余"+queue.size()+"個(gè)元素");
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
while(true){
synchronized (queue) {
while(queue.size() == queueSize){
try {
System.out.println("隊(duì)列滿,等待有空余空間");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.offer(1); //每次插入一個(gè)元素
queue.notify();
System.out.println("向隊(duì)列取中插入一個(gè)元素欠痴,隊(duì)列剩余空間:"+(queueSize-queue.size()));
}
}
}
}
}
2.使用Condition實(shí)現(xiàn)
public class Test {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
while(true){
lock.lock();
try {
while(queue.size() == 0){
try {
System.out.println("隊(duì)列空迄靠,等待數(shù)據(jù)");
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll(); //每次移走隊(duì)首元素
notFull.signal();
System.out.println("從隊(duì)列取走一個(gè)元素,隊(duì)列剩余"+queue.size()+"個(gè)元素");
} finally{
lock.unlock();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
while(true){
lock.lock();
try {
while(queue.size() == queueSize){
try {
System.out.println("隊(duì)列滿喇辽,等待有空余空間");
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1); //每次插入一個(gè)元素
notEmpty.signal();
System.out.println("向隊(duì)列取中插入一個(gè)元素掌挚,隊(duì)列剩余空間:"+(queueSize-queue.size()));
} finally{
lock.unlock();
}
}
}
}
}