Java暫停/掛起執行緒(suspend())和恢復執行緒(resume())

2020-07-16 10:04:39
暫停執行緒意味著此執行緒還可以恢復執行。在 Java 多執行緒中,可以使用 suspend() 方法暫停執行緒,使用 resume() 方法恢復執行緒的執行。

suspend() 與 resume() 方法

本節通過一個案例來介紹 suspend() 與 resume() 方法的用法。首先來看一下案例中使用到的 MyThread21 執行緒,程式碼如下所示。
package ch14;
public class MyThread21 extends Thread
{
    private long i=0;
    public long getI()
    {
        return i;
    }
    public void setI(long i)
    {
        this.i=i;
    }
    @Override
    public void run()
    {
        while(true)
        {
            i++;
        }
    }
}

MyThread21 執行緒中有一個成員 i,其中 setI() 方法和 getI() 方法分別用於設定和獲取 i 的值,run() 方法則是一個從i開始遞增的死迴圈。

下面編寫主執行緒的程式碼,具體如下所示。
package ch14;
public class Test25
{
    public static void main(String[] args)
    {
        try
        {
            MyThread21 thread=new MyThread21();
            thread.start();
            Thread.sleep(5000);
            //A段
            thread.suspend();
            System.out.println("A= "+System.currentTimeMillis()+" i= "+thread.getI());
            Thread.sleep(5000);
            System.out.println("A= "+System.currentTimeMillis()+" i= "+thread.getI());
            //B段
            thread.resume();
            Thread.sleep(5000);
            //C段
            thread.suspend();
            System.out.println("B= "+System.currentTimeMillis()+" i= "+thread.getI());
            Thread.sleep(5000);
            System.out.println("B= "+System.currentTimeMillis()+" i= "+thread.getI());
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

最終執行結果如下所示。
A= 1540978346179 i= 2680986095
A= 1540978351179 i= 2680986095
B= 1540978356179 i= 5348657508
B= 1540978361179 i= 5348657508

從輸出結果的時間來看,呼叫 suspend() 方法確實可以暫停執行緒,而在呼叫 resume() 方法後執行緒恢復執行狀態。

獨占問題

在使用 suspend() 方法與 resume() 方法時,如果使用不當極易造成公共的同步物件被獨占,從而使得其他執行緒無法存取公共同步物件。

例 2

下面通過一個案例來演示這種情況。如下所示是案例中使用的公共物件的程式碼。
package ch14;
public class SynchronizedObject1
{
    synchronized public void printString()
    {
        System.out.println("begin");
        if (Thread.currentThread().getName().equals("a"))
        {
            System.out.println("a執行緒永遠 suspend了!");
            Thread.currentThread().suspend();
        }
        System.out.println("end");
    }
}

SynchronizedObject1 類在 printString() 方法開始時輸出“begin”,在該方法結束時輸出“end”,方法體中的if判斷如果當前執行緒的名稱是 a 則呼叫 suspend() 方法暫停執行緒。

接下來編寫主執行緒程式碼,在主執行緒中建立一個 SynchronizedObject1 類物件並線上程中呼叫 printString() 方法,具體程式碼如下所示。
package ch14;
public class Test26
{
    public static void main(String[] args)
    {
        try
        {
            final SynchronizedObject1 object=new SynchronizedObject1();
            Thread thread1=new Thread()
            {
                @Override
                public void run()
                {
                    object.printString();
                }
            };
            thread1.setName("a");
            thread1.start();
            Thread.sleep(1000);
            Thread thread2=new Thread()
            {
                @Override
                public void run()
                {
                    System.out.println("thread2啟動了,但進入不了printString()方法!所以只會列印1個begin!");
                    System.out.println("因為printString()方法被a執行緒鎖定並且永遠暫停了!");
                    object.printString();
                }
            };
            thread2.start();
        }
        catch(InterruptedException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

上述程式碼比較簡單這裡就不再解釋,執行後的結果如下所示。從中可以看到由於執行緒被永久暫停,所以只會輸出一個 begin。
begin
a執行緒永遠 suspend了!
thread2啟動了,但進入不了printString()方法!所以只會列印1個begin!
因為printString()方法被a執行緒鎖定並且永遠暫停了!

例 3

還有另外一種獨占鎖的情況也要格外注意,稍有不慎就會掉進“坑”裡。建立測試用的 MyThread22 執行緒,具體程式碼如下:
package ch14;
public class MyThread22 extends Thread
{
    private long i=0;
    @Override
    public void run()
    {
        while (true)
        {
            i++;
        }
    }
}

再來看主執行緒的程式碼,如下所示。
package ch14;
public class Test27
{
    public static void main(String[] args)
    {
        try
        {
            MyThread22 thread=new MyThread22();
            thread.start();
            Thread.sleep(1000);
            thread.suspend();
            System.out.println("main end!");
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

程式執行後將看到預料不到的結果,如下所示。
main end!

如果將 MyThread22 執行緒類的程式碼更改如下:
package ch14;
public class MyThread22 extends Thread
{   
    private long i=0; 
    @Override 
    public void run()
    { 
        while(true)
        { 
            i++; 
            System.out.println(i); 
        } 
    }
}

再次執行程式,控制台將不列印 main end,執行結果如下所示。
......
130862
130863
130864
130865
130866
130867

出現這種情況的原因是,當程式執行到 println() 方法內部停止時,同步鎖未被釋放。這導致當前 PrintStream 物件的 println() 方法一直呈“暫停”狀態,並且“鎖未釋放”,而 main() 方法中的程式碼“System.out. println(Mmain end!'1);”遲遲不能執行列印。

提示:雖然 suspend() 方法是過期作廢的方法,但還是有必要研究它過期作廢的原因,這是很有意義的。

不同步問題

在使用 suspend() 方法與 resume() 方法時也容易出現因為執行緒的暫停而導致資料不同步的情況。

例 4

下面通過一個案例來演示這種情況。如下所示是案例中使用的公共物件的程式碼。
package ch14;
public class MyObject
{
    private String username="1";
    private String password="11";
    public void setValue(String u,String p)
    {
        this.username=u;
        if(Thread.currentThread().getName().equals("a"))
        {
            System.out.println("停止a執行緒!");
            Thread.currentThread().suspend();
        }
        this.password=p;
    }
    public void printUsernamePassword()
    {
        System.out.println(username+" "+password);
    }
}

如上述程式碼所示,MyObject 類的 setValue() 方法會線上程名稱是 a 時執行停止執行緒操作。 如下所示是主執行緒程式碼。
package ch14;
public class Test28
{
    public static void main(String[] args) throws InterruptedException
    {
        final MyObject myobject=new MyObject();
        Thread thread1=new Thread()
        {
            public void run()
            {
                myobject.setValue("a","aa");
            };
        };
        thread1.setName("a");
        thread1.start();
        Thread.sleep(500);
        Thread thread2=new Thread()
        {
            public void run()
            {
                myobject.printUsernamePassword();
            };
        };
        thread2.start();
    }
}

程式執行結果如下所示。
停止a執行緒!
a 11

從程式執行的結果可以看到,出現了值不同步的情況,所以在程式中使用 suspend() 方法要格外注意。