在上一篇文章中(Java併發編程:線程的基本狀態)我們介紹了線程狀態的 5 種基本狀態以及線程的聲明週期。這篇文章將深入講解Java如何對線程進行狀態控制,比如:如何將一個線程從一個狀態轉到另一個狀態,如何設置線程的優先級等。
一、join() 等待阻塞
讓一個線程等待另一個線程完成才繼續執行。如A線程線程執行體中調用B線程的join()方法,則A線程被阻塞,知道B線程執行完爲止,A才能得以繼續執行。
package com.chanshuyi.thread;
public class ThreadDemo6 {
public static void main(String[] args) {
//I'm the thread.
//Main Thread is Running over.
Thread thread = new Thread(){
@Override
public void run(){
System.out.println("I'm the thread.");
try{
Thread.sleep(2000); //讓其休眠2秒,測試主線程是否等待線程執行完再執行
}catch(Exception e){
e.printStackTrace();
}
}
};
thread.start();
try {
thread.join(); //讓main線程等待線程執行完再執行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Main Thread is Running over.");
}
}
代碼中我特意讓子線程休眠了2秒,但是最終的結果還是main線程等待子線程運行後再繼續運行。
二、wait()/notify() 鎖阻塞
wait() 和 notify() 方法的調用需要調用的方法有一個鎖對象,主要用於進行不同線程之間的同步協作,常見的有生產者消費者模型。
package com.chanshuyi.thread;
/**
* 生產者消費者模型
* @author Administrator
*
*/
public class ThreadDemo92 {
public static void main(String[] args) {
final ProduceConsumer pc = new ProduceConsumer();
//生產者線程
new Thread(){
@Override
public void run(){
//生產10次
for(int i = 0; i < 5; i++){
System.out.println("Ready to Produce:" + (i + 1));
pc.produce(i);
}
}
}.start();
//消費者線程
new Thread(){
@Override
public void run(){
//消費10次
for(int j = 0; j < 5; j++){
System.out.println("Ready to Consume:" + (j + 1));
pc.consume(j);
}
}
}.start();
}
}
class ProduceConsumer{
//生產者
public synchronized void produce(int i){
if(isEmpty){
//沒東西了,可以生產
num = (int)(Math.random() * 100);
System.out.println("Producer:" + (i + 1) + "," + num);
isEmpty = false;
notify();
}else{
try {
System.out.println("producer執行wait操作:" + (i + 1));
wait();
System.out.println("producer醒來:" + (i + 1));
num = (int)(Math.random() * 100);
System.out.println("Producer:" + (i + 1) + "," + num);
isEmpty = false;
notify();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//消費者
public synchronized void consume(int i){
if(!isEmpty){
System.out.println("Consumer:" + (i + 1) + "," + num);
isEmpty = true;
notify();
}else{
try {
System.out.println("consumer執行wait操作:" + (i + 1));
wait();
System.out.println("consumer醒來:" + (i + 1));
System.out.println("Consumer:" + (i + 1) + "," + num);
isEmpty = true;
notify();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public ProduceConsumer(){
isEmpty = true; //默認爲空
}
private boolean isEmpty; //是否爲空
private int num; //生產的東西
public boolean isEmpty() {
return isEmpty;
}
public void setEmpty(boolean isEmpty) {
this.isEmpty = isEmpty;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
線程同步屬於線程的一個非常重要的知識,而且也相對比較複雜,這裏只做一個簡單的介紹,後面會有更加詳細的講解。
三、sleep() 其他類型阻塞
讓當前的正在執行的線程暫停指定的時間,並進入阻塞狀態。直接使用 Thread.sleep(long millionSeconds) 就可以了
package com.chanshuyi.thread;
public class ThreadDemo7 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
System.out.println("Sleep 2 Seconds.");
}
};
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Awake");
}
}
四、yield() 線程讓步
將線程從運行狀態轉換爲就緒狀態。
當某個線程調用 yiled() 方法從運行狀態轉換到就緒狀態後,CPU 會從就緒狀態線程隊列中只會選擇與該線程優先級相同或優先級更高的線程去執行。
在使用時直接用 Thread.yield() 靜態方法就可以了。一般情況下我們的 CPU 都很難達到 100% 的利用率,所以當我們使用 yield() 方法將線程掛起之後,一般又會立即獲得資源,繼續執行,因此很難寫個程序去進行驗證。而且這個方法在工作中也是比較少用到,所以只需要瞭解其作用就可以了。
sleep() 和 yield() 兩者的區別:
- ①
sleep()方法會給其他線程運行的機會,不考慮其他線程的優先級,因此會給較低優先級線程一個運行的機會。yield()方法只會給相同優先級或者更高優先級的線程一個運行的機會。 - ② 當線程執行了 sleep(long millis)
方法,將轉到阻塞狀態,參數millis指定睡眠時間。當線程執行了yield()方法,將轉到就緒狀態。 - ③ sleep() 方法聲明拋出InterruptedException異常,而 yield() 方法沒有聲明拋出任何異常。
package com.chanshuyi.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo8 {
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
Thread thread1 = new Thread(){
@Override
public void run(){
System.out.println("Enter thread1.");
lock.lock();
System.out.println("Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.yield();
System.out.println("I'm the thread1");
lock.unlock();
}
};
Thread thread2 = new Thread(){
@Override
public void run(){
System.out.println("Enter thread2.");
lock.lock();
System.out.println("Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.yield();
System.out.println("I'm the thread2");
lock.unlock();
}
};
Thread thread3 = new Thread(){
@Override
public void run(){
System.out.println("Enter thread3.");
lock.lock();
System.out.println("Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.yield();
System.out.println("I'm the thread3");
lock.unlock();
}
};
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.NORM_PRIORITY);
thread3.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
在上面中,我們讓3個線程競爭一個鎖。其中一個線程隨機獲得lock鎖,之後休眠兩秒等待其他2個線程進入Lock Block狀態。之後獲得鎖lock鎖的線程調用yield()釋放鎖,這個時候,應該是優先級最高的那個線程獲得鎖,但實際上卻不是這樣的,具體原因我也沒分析出來。下面是一些執行結果,感興趣的朋友可以分析一下。
Enter thread2.
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
Enter thread3.
Enter thread1.
I'm the thread2
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1
Enter thread2.
Enter thread3.
Enter thread1.
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
I'm the thread2
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1
Enter thread3.
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
Enter thread1.
Enter thread2.
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
I'm the thread2
Enter thread2.
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
Enter thread3.
Enter thread1.
I'm the thread2
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1
五、setPriority() 改變線程的優先級
每個線程在執行時都具有一定的優先級,優先級高的線程具有較多的執行機會。每個線程默認的優先級都與創建它的線程的優先級相同。main線程默認具有普通優先級。
設置線程優先級:setPriority(int priorityLevel)。參數priorityLevel範圍在1-10之間,常用的有如下三個靜態常量值:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
獲取線程優先級:getPriority()。
注:具有較高線程優先級的線程對象僅表示此線程具有較多的執行機會,而非優先執行。
package com.chanshuyi.thread;
public class ThreadDemo7 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
System.out.println("I'm the Priority Test Thread.");
}
};
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
}
六、setDaemon(true) 設置爲後臺線程
概念/目的:後臺線程主要是爲其他線程(相對可以稱之爲前臺線程)提供服務,或“守護線程”。如JVM中的垃圾回收線程。
生命週期:後臺線程的生命週期與前臺線程生命週期有一定關聯。主要體現在:當所有的前臺線程都進入死亡狀態時,後臺線程會自動死亡(其實這個也很好理解,因爲後臺線程存在的目的在於爲前臺線程服務的,既然所有的前臺線程都死亡了,那它自己還留着有什麼用…偉大啊 ! !)。
設置後臺線程:調用Thread對象的setDaemon(true)方法可以將指定的線程設置爲後臺線程。
package com.chanshuyi.thread;
public class ThreadDemo91 {
public static void main(String[] args) {
//輸出:Main Thread is going to die
Thread thread = new Thread(){
@Override
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Hello, I'm background thread.");
}
};
thread.setDaemon(true);
thread.start();
System.out.println("Main Thread is going to die.");
}
}
上面的代碼中存在兩個線程,一個是Main線程,是前臺線程,一個是我們創建的後臺線程。我們在後臺線程中故意使其休眠了1秒,而在這1秒鐘內前臺線程Main已經執行完畢了,所以後臺線程也就直接結束了”Main Thread is going to die.“,而不會輸出後臺線程中的語句。
判斷線程是否是後臺線程:調用thread對象的isDeamon()方法。
注:main線程默認是前臺線程,前臺線程創建中創建的子線程默認是前臺線程,後臺線程中創建的線程默認是後臺線程。調用setDeamon(true)方法將前臺線程設置爲後臺線程時,需要在start()方法調用之前,否則一但線程運行,將無法改變其類型。前天線程都死亡後,JVM通知後臺線程死亡,但從接收指令到作出響應,需要一定的時間。
參考博文:
http://www.cnblogs.com/lwbqqyumidi/p/3817517.html