1. 線程的五大狀態及其轉換
線程的五大狀態分別爲:創建狀態(New)、就緒狀態(Runnable)、運行狀態(Running)、阻塞狀態(Blocked)、死亡狀態(Dead)。
下面畫出線程五大狀態之間的關係圖:
(1)新建狀態:即單純地創建一個線程,創建線程有三種方式,在我的博客:線程的創建,可以自行查看!
(2)就緒狀態:在創建了線程之後,調用Thread類的start()方法來啓動一個線程,即表示線程進入就緒狀態!
(3)運行狀態:當線程獲得CPU時間,線程才從就緒狀態進入到運行狀態!
(4)阻塞狀態:線程進入運行狀態後,可能由於多種原因讓線程進入阻塞狀態,如:調用sleep()方法讓線程睡眠,調用wait()方法讓線程等待,調用join()方法、suspend()方法(它現已被棄用!)以及阻塞式IO方法。
(5)死亡狀態:run()方法的正常退出就讓線程進入到死亡狀態,還有當一個異常未被捕獲而終止了run()方法的執行也將進入到死亡狀態!
2. 設置或獲取多線程的線程名稱的方法
由於在一個進程中可能有多個線程,而多線程的運行狀態又是不確定的,即不知道在多線程中當前執行的線程是哪個線程,所以在多線程操作中需要有一個明確的標識符標識出當前線程對象的信息,這個信息往往通過線程的名稱來描述。在Thread類中提供了一些設置或獲取線程名稱的方法:
(1)創建線程時設置線程名稱:
public Thread(Runnable target,String name)
(2)設置線程名稱的普通方法:
public final synchronized void setName(String name)
(3)取得線程名稱的普通方法:
public final String getName()
下面將在代碼中使用以上方法,作爲演示:
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++)
{
//currentThread()方法用於取得當前正在JVM中運行的線程
//使用getName()方法,用於獲取線程的名稱
System.out.println("當前線程:"+Thread.currentThread().getName()+"-----i="+i);
}
}
}
public class Test1 {
public static void main(String[] args){
//創建線程對象thread1且沒有設置線程名稱
MyThread myThread1=new MyThread();
Thread thread1=new Thread(myThread1);
thread1.start();
//創建線程對象thread2且使用setName設置線程名稱
MyThread myThread2=new MyThread();
Thread thread2=new Thread(myThread2);
thread2.setName("線程2");
thread2.start();
//創建線程對象thread3並在創建線程時設置線程名稱
MyThread myThread3=new MyThread();
Thread thread3=new Thread(myThread3,"線程3");
thread3.start();
}
}
某次運行結果如下所示:
當前線程:Thread-0-----i=0
當前線程:Thread-0-----i=1
當前線程:Thread-0-----i=2
當前線程:線程3-----i=0
當前線程:線程2-----i=0
當前線程:線程2-----i=1
當前線程:線程2-----i=2
當前線程:線程2-----i=3
當前線程:線程3-----i=1
當前線程:Thread-0-----i=3
當前線程:Thread-0-----i=4
當前線程:線程3-----i=2
當前線程:線程3-----i=3
當前線程:線程3-----i=4
當前線程:線程2-----i=4
通過上述代碼及其運行結果可知:
(1)若沒有手動設置線程名稱時,會自動分配一個線程的名稱,如線程對象thread1自動分配線程名稱爲Thread-0。
(2)多線程的運行狀態是不確定的,不知道下一個要執行的是哪個線程,這是因爲CPU以不確定方式或以隨機的時間調用線程中的run()方法。
(3)需要注意的是,由於設置線程名稱是爲了區分當前正在執行的線程是哪一個線程,所以在設置線程名稱時應避免重複!
3. 線程休眠------sleep()方法
線程休眠:指的是讓線程暫緩執行,等到預計時間之後再恢復執行。
(1)線程休眠會交出CPU,讓CPU去執行其他的任務。
(2)調用sleep()方法讓線程進入休眠狀態後,sleep()方法並不會釋放鎖,即當前線程持有某個對象鎖時,即使調用sleep()方法其他線程也無法訪問這個對象。
(3)調用sleep()方法讓線程從運行狀態轉換爲阻塞狀態;sleep()方法調用結束後,線程從阻塞狀態轉換爲可執行狀態。
sleep()方法:
public static native void sleep(long millis) throws InterruptedException;
從上面方法參數中可以看出sleep()方法的休眠時間是以毫秒作爲單位。
關於sleep()方法的操作如下:
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++)
{
//使用Thread類的sleep()方法,讓線程處於休眠狀態
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("當前線程:"+Thread.currentThread().getName()+"-----i="+i);
}
}
}
public class Test1 {
public static void main(String[] args){
MyThread myThread=new MyThread();
//利用myThread對象分別創建三個線程
Thread thread1=new Thread(myThread);
thread1.start();
Thread thread2=new Thread(myThread);
thread2.start();
Thread thread3=new Thread(myThread);
thread3.start();
}
}
某次運行結果如下所示:
當前線程:Thread-2-----i=0
當前線程:Thread-1-----i=0
當前線程:Thread-0-----i=0
當前線程:Thread-2-----i=1
當前線程:Thread-0-----i=1
當前線程:Thread-1-----i=1
當前線程:Thread-2-----i=2
當前線程:Thread-1-----i=2
當前線程:Thread-0-----i=2
當前線程:Thread-2-----i=3
當前線程:Thread-1-----i=3
當前線程:Thread-0-----i=3
當前線程:Thread-2-----i=4
當前線程:Thread-0-----i=4
當前線程:Thread-1-----i=4
注:
(1)通過運行代碼進行觀察,發現運行結果會等待一段時間,這就是sleep()方法讓原本處於運行狀態的線程進入了休眠,從而進程的狀態從運行狀態轉換爲阻塞狀態。
(2)以上代碼創建的三個線程肉眼觀察,發現它們好像是同時進入休眠狀態,但其實並不是同時休眠的。
4. 線程讓步------yield()方法
線程讓步:暫停當前正在執行的線程對象,並執行其他線程。
(1)調用yield()方法讓當前線程交出CPU權限,讓CPU去執行其他線程。
(2)yield()方法和sleep()方法類似,不會釋放鎖,但yield()方法不能控制具體交出CPU的時間。
(3)yield()方法只能讓擁有相同優先級的線程獲取CPU執行的機會。
(4)使用yield()方法不會讓線程進入阻塞狀態,而是讓線程從運行狀態轉換爲就緒狀態,只需要等待重新獲取CPU執行的機會。
yield()方法:
public static native void yield();
關於yield()方法的操作如下:
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++)
{
//使用Thread類的yield()方法
Thread.yield();
System.out.println("當前線程:"+Thread.currentThread().getName()+"-----i="+i);
}
}
}
public class Test1 {
public static void main(String[] args){
MyThread myThread=new MyThread();
//利用myThread對象分別創建三個線程
Thread thread1=new Thread(myThread);
thread1.start();
Thread thread2=new Thread(myThread);
thread2.start();
Thread thread3=new Thread(myThread);
thread3.start();
}
}
某次運行結果如下所示:
當前線程:Thread-0-----i=0
當前線程:Thread-0-----i=1
當前線程:Thread-0-----i=2
當前線程:Thread-0-----i=3
當前線程:Thread-0-----i=4
當前線程:Thread-1-----i=0
當前線程:Thread-2-----i=0
當前線程:Thread-2-----i=1
當前線程:Thread-2-----i=2
當前線程:Thread-2-----i=3
當前線程:Thread-2-----i=4
當前線程:Thread-1-----i=1
當前線程:Thread-1-----i=2
當前線程:Thread-1-----i=3
當前線程:Thread-1-----i=4
5. 等待線程終止------join()方法
等待線程終止:指的是如果在主線程中調用該方法時就會讓主線程休眠,讓調用join()方法的線程先執行完畢後再開始執行主線程。
join()方法:
public final void join() throws InterruptedException {
join(0);
}
注:上面的join()方法是不帶參數的,但join()方法還可以帶參數,下去自行了解!
關於join()方法的操作如下:
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<2;i++)
{
//使用Thread類的sleep()方法
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("當前線程:"+Thread.currentThread().getName()+"-----i="+i);
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"自己創建的線程");
thread1.start();
System.out.println("主線程:"+Thread.currentThread().getName());
//線程對象thread1調用join()方法
thread1.join();
System.out.println("代碼結束");
}
}
運行結果如下所示:
主線程:main
當前線程:自己創建的線程-----i=0
當前線程:自己創建的線程-----i=1
代碼結束
若不調用join()方法的話,運行結果如下所示:
主線程:main
代碼結束
當前線程:自己創建的線程-----i=0
當前線程:自己創建的線程-----i=1
故通過兩個運行結果可以更加深刻地感受到調用join()方法後的作用!調用join()方法和不調用join()方法的區別!
6. 線程停止
多線程中停止線程有三種方式:
(1)設置標記位,讓線程正常停止。
class MyThread implements Runnable{
//設置標記位
private boolean flag=true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
int i=0;
while(flag)
{
System.out.println("第"+(i++)+"次執行-----"+"線程名稱:"+Thread.currentThread().getName());
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"自己創建的線程");
thread1.start();
//讓主線程sleep一毫秒
Thread.sleep(1);
//修改標記位的值,讓自己創建的線程停止
myThread.setFlag(false);
System.out.println("代碼結束");
}
}
運行結果如下所示:
第0次執行-----線程名稱:自己創建的線程
第1次執行-----線程名稱:自己創建的線程
第2次執行-----線程名稱:自己創建的線程
第3次執行-----線程名稱:自己創建的線程
第4次執行-----線程名稱:自己創建的線程
第5次執行-----線程名稱:自己創建的線程
第6次執行-----線程名稱:自己創建的線程
第7次執行-----線程名稱:自己創建的線程
第8次執行-----線程名稱:自己創建的線程
第9次執行-----線程名稱:自己創建的線程
第10次執行-----線程名稱:自己創建的線程
第11次執行-----線程名稱:自己創建的線程
第12次執行-----線程名稱:自己創建的線程
第13次執行-----線程名稱:自己創建的線程
第14次執行-----線程名稱:自己創建的線程
第15次執行-----線程名稱:自己創建的線程
第16次執行-----線程名稱:自己創建的線程
第17次執行-----線程名稱:自己創建的線程
第18次執行-----線程名稱:自己創建的線程
第19次執行-----線程名稱:自己創建的線程
第20次執行-----線程名稱:自己創建的線程
第21次執行-----線程名稱:自己創建的線程
第22次執行-----線程名稱:自己創建的線程
第23次執行-----線程名稱:自己創建的線程
第24次執行-----線程名稱:自己創建的線程
第25次執行-----線程名稱:自己創建的線程
第26次執行-----線程名稱:自己創建的線程
代碼結束
(2)使用stop()方法強制使線程退出,但是使用該方法不安全,已經被廢棄了!
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++)
{
System.out.println("線程名稱:"+Thread.currentThread().getName()+"------i="+i);
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"自己創建的線程");
thread1.start();
//讓主線程sleep一毫秒
Thread.sleep(1);
//調用已被棄用的stop()方法去強制讓線程退出
thread1.stop();
System.out.println("代碼結束");
}
}
某次運行結果如下所示:
線程名稱:自己創建的線程------i=0
線程名稱:自己創建的線程------i=1
線程名稱:自己創建的線程------i=2
線程名稱:自己創建的線程------i=3
線程名稱:自己創建的線程------i=4
線程名稱:自己創建的線程------i=5
線程名稱:自己創建的線程------i=6
線程名稱:自己創建的線程------i=7
線程名稱:自己創建的線程------i=8
線程名稱:自己創建的線程------i=9
線程名稱:自己創建的線程------i=10
線程名稱:自己創建的線程------i=11
線程名稱:自己創建的線程------i=12
線程名稱:自己創建的線程------i=13
線程名稱:自己創建的線程------i=14
線程名稱:自己創建的線程------i=15
線程名稱:自己創建的線程------i=16
線程名稱:自己創建的線程------i=17
線程名稱:自己創建的線程------i=18
線程名稱:自己創建的線程------i=19
線程名稱:自己創建的線程------i=20
線程名稱:自己創建的線程------i=21
線程名稱:自己創建的線程------i=22
線程名稱:自己創建的線程------i=23
線程名稱:自己創建的線程------i=24
線程名稱:自己創建的線程------i=25
線程名稱:自己創建的線程------i=26
線程名稱:自己創建的線程------i=27
線程名稱:自己創建的線程------i=28
線程名稱:自己創建的線程------i=29
線程名稱:自己創建的線程------i=30
線程名稱:自己創建的線程------i=31
線程名稱:自己創建的線程------i=32
線程名稱:自己創建的線程------i=33
線程名稱:自己創建的線程------i=34
線程名稱:自己創建的線程------i=35
線程名稱:自己創建的線程------i=36
線程名稱:自己創建的線程------i=37
線程名稱:自己創建的線程------i=38
線程名稱:自己創建的線程------i=39
線程名稱:自己創建的線程------i=40
線程名稱:自己創建的線程------i=41
線程名稱:自己創建的線程------i=42
線程名稱:自己創建的線程------i=43
線程名稱:自己創建的線程------i=44
線程名稱:自己創建的線程------i=45
線程名稱:自己創建的線程------i=46
線程名稱:自己創建的線程------i=47
線程名稱:自己創建的線程------i=48
線程名稱:自己創建的線程------i=49
線程名稱:自己創建的線程------i=50
線程名稱:自己創建的線程------i=51線程名稱:自己創建的線程------i=51代碼結束
從上述代碼和運行結果可以看出,原本線程對象thread1的run()方法中應該執行100次語句“System.out.println("線程名稱:"+Thread.currentThread().getName()+"------i="+i);”,但現在沒有執行夠100次,所以說stop()方法起到了讓線程終止的作用。再從運行結果上可以看出,i=51被執行了兩次且沒有換行,這就體現了調用stop()方法的不安全性!
下面正式地解釋stop()方法爲什麼不安全?
因爲stop()方法會解除由線程獲得的所有鎖,當在一個線程對象上調用stop()方法時,這個線程對象所運行的線程會立即停止,假如一個線程正在執行同步方法:
public synchronized void fun(){
x=3;
y=4;
}
由於方法是同步的,多線程訪問時總能保證x,y被同時賦值,而如果一個線程正在執行到x=3;時,被調用的stop()方法使得線程即使在同步方法中也要停止,這就造成了數據的不完整性。故,stop()方法不安全,已經被廢棄,不建議使用!
(3)使用Thread類的interrupt()方法中斷線程。
class MyThread implements Runnable{
@Override
public void run() {
int i=0;
while(true)
{
//使用sleep()方法,使得線程由運行狀態轉換爲阻塞狀態
try {
Thread.sleep(1000);
//調用isInterrupted()方法,用於判斷當前線程是否被中斷
boolean bool=Thread.currentThread().isInterrupted();
if(bool) {
System.out.println("非阻塞狀態下執行該操作,當前線程被中斷!");
break;
}
System.out.println("第"+(i++)+"次執行"+" 線程名稱:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println("退出了!");
//這裏退出了阻塞狀態,且中斷標誌bool被系統自動清除設置爲false,所以此處的bool爲false
boolean bool=Thread.currentThread().isInterrupted();
System.out.println(bool);
//退出run()方法,中斷進程
return;
}
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"自己創建的線程");
thread1.start();
//讓主線程sleep三秒
Thread.sleep(3000);
//調用interrupt()方法
thread1.interrupt();
System.out.println("代碼結束");
}
}
運行結果如下所示 :
第0次執行 線程名稱:自己創建的線程
第1次執行 線程名稱:自己創建的線程
第2次執行 線程名稱:自己創建的線程
代碼結束
退出了!
false
(1)interrupt()方法只是改變中斷狀態而已,它不會中斷一個正在運行的線程。具體來說就是,調用interrupt()方法只會給線程設置一個爲true的中斷標誌,而設置之後,則根據線程當前狀態進行不同的後續操作。
(2)如果線程的當前狀態出於非阻塞狀態,那麼僅僅將線程的中斷標誌設置爲true而已;
(3)如果線程的當前狀態出於阻塞狀態,那麼將在中斷標誌設置爲true後,還會出現wait()、sleep()、join()方法之一引起的阻塞,那麼會將線程的中斷標誌位重新設置爲false,並拋出一個InterruptedException異常。
(4)如果在中斷時,線程正處於非阻塞狀態,則將中斷標誌修改爲true,而在此基礎上,一旦進入阻塞狀態,則按照阻塞狀態的情況來進行處理。例如,一個線程在運行狀態時,其中斷標誌設置爲true之後,一旦線程調用了wait()、sleep()、join()方法中的一種,立馬拋出一個InterruptedException異常,且中斷標誌被程序自動清除,重新設置爲false。
總結:調用Thread類的interrupted()方法,其本質只是設置該線程的中斷標誌,將中斷標誌設置爲true,並根據線程狀態決定是否拋出異常。因此,通過interrupted()方法真正實現線程的中斷原理是 :開發人員根據中斷標誌的具體值來決定如何退出線程。
以上是我所介紹的關於線程的五大狀態和常用方法,關於狀態轉換中的synchronized關鍵字以及wait()方法、notify()方法、notifyAll()方法在下一篇博客中介紹!