Java 線程通信的安全問題(同步)

線程間的通信 : 其實就是多個線程操作同一資源,但是操作的動作不同

注意:
1、多個線程
2、同一資源
3、操作動作不同

例如一個場景: 輸入線程(Input) 和 輸出線程(outPut) ,輸入線程負責操作資源(Resource),Resource 有 name(String) 和 sex(String)兩個變量,輸入線程(Input) 負責爲 Resource 的成員賦值,輸出線程 (outPut)打印值

分析 : 按照正常的場景來說,輸入線程(Input)一旦爲資源(Resource)賦值完成後,輸出線程(outPut) 應該打印資源(Resource)的成員變量值。同一資源,可通過構造函數將資源傳入線程。

先看如下代碼:

//資源
class Resource
{
    String name;

    String sex;
}

//輸入線程
class Input implements Runnable
{
    private Resource res;

    // 保證 同一資源
    public Input(Resource res)
    {
        this.res = res;
    }

    public void run()
    {
        int  x = 0;

        // 死循環的原因: 打印越多越容易看出問題所在
        while(true)
        {

                if(x==0)
                {
                    res.name ="jack";
                    res.sex = "man";
                }
                else
                {
                    res.name = "傑克";
                    res.sex = "男";
                }

                // 目的在於做切換,賦值不同是爲了更好區分是否存在問題
                x = (x+1)%2;

        }
    }
}


//輸出線程
class outPut implements Runnable
{

    private Resource res;

    public outPut(Resource res)
    {
        this.res = res;
    }  


    public void run() 
    {

        while(true)
        {

        System.out.println("輸出線程:"+this.res.name+"..."+this.res.sex);


        }
    }

}


//主函數
public class ThreadWaitAndNotify
{

    public static void main(String[] args) 
    {
        //資源
        Resource res = new Resource();

        Input input = new Input(res);
        outPut output = new outPut(res);


        // 輸入線程
        Thread t1 = new Thread(input);
        //輸出線程
        Thread t2 = new Thread(output);

        t1.start();
        t2.start();

    }

}

看下打印結果(截取部分):

輸出線程:傑克...man
輸出線程:jack...男
輸出線程:傑克...男
輸出線程:jack...man
輸出線程:傑克...男
輸出線程:傑克...man

分析: 從打印結果上來說,沒達到我們的要求,正常來說,應該是中文對應中文,英文對應英文輸出纔對 ! 那爲什麼會出現如下問題呢 ? 其實,原因很簡單,假設輸入線程 (Input)第一次 賦值 name = “jack”,sex = “man”,此時輸入線程(Input)繼續享有執行權,做切換賦值,當賦值到 name = “傑克”時,即將進行sex = “男”賦值時,失去了執行權,此時,cpu執行權被輸出線程(outPut)拿到,輸出操作資源(res)的成員變量值,由於是操作同一資源,此時資源(res)的 name = “傑克”, sex = “man”,將其輸出。導致瞭如上結果

解決方案: 同步。那麼同步應該注意哪些地方呢 ?
( 1 ) 首先明白 線程 對資源的操作 代碼 是哪些,這個比較好解決,就是 run方法的代碼。
( 2 ) 其次,鎖必須唯一。那有兩種選擇,可以是res對象,也可以是字節碼文件對象(在內存中,InPut.class和ouput.class也是唯一存在的)

修改代碼如下:

//資源
class Resource
{
    String name;

    String sex;
}

//輸入線程
class Input implements Runnable
{
    private Resource res;

    public Input(Resource res)
    {
        this.res = res;
    }

    public void run()
    {
        int  x = 0;

        while(true)
        {
            synchronized (res) 
            {

                if(x==0)
                {
                    res.name ="jack";
                    res.sex = "man";
                }
                else
                {
                    res.name = "傑克";
                    res.sex = "男";
                }

                x = (x+1)%2;
            }




        }
    }
}

//輸出線程
class outPut implements Runnable
{

    private Resource res;

    public outPut(Resource res)
    {
        this.res = res;
    }  

    @Override
    public void run() 
    {

        while(true)
        {
            synchronized (res) 
            {
                System.out.println("輸出線程:"+this.res.name+"..."+this.res.sex);
            }

        }
    }

}


//主函數
public class ThreadWaitAndNotify
{

    public static void main(String[] args) 
    {
        Resource res = new Resource();

        Input input = new Input(res);

        outPut output = new outPut(res);


        Thread t1 = new Thread(input);
        Thread t2 = new Thread(output);

        t1.start();
        t2.start();

    }

}

打印結果(截取部分):

輸出線程:jack...man
輸出線程:jack...man
輸出線程:jack...man
輸出線程:jack...man
輸出線程:傑克...男
輸出線程:傑克...男
輸出線程:傑克...

線程安全問題是解決了,但是,跟我們預期的結果還是有些差距? 回到最初文章開頭我描述的場景,我們期望的結果是,輸入線程(Input)順利對操作資源(res)賦值完成後,輸出線程(outPut)馬上能夠打印輸入線程的結果。

如下:

輸出線程:jack...man
輸出線程:傑克...男
輸出線程:jack...man
輸出線程:傑克...男
輸出線程:jack...man
輸出線程:傑克...男
輸出線程:jack...man
.
.
.

爲什麼我們解決了線程安全問題,但是沒達到我們想要的結果呢?
分析這個原因也很簡單 : 當輸入線程(Input)操作資源(res)完成後,假設輸入線程(Input)這時候失去執行權,而輸出線程(outPut)在短時間內得到了執行權,就會瘋狂打印這個結果。如何解決呢?這個涉及到一個知識點,線程的等待喚醒機制。下篇博文介紹。

本篇博文相關代碼放這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章