黑馬程序員-多線程部分(三.等待喚醒機制)

 ——- android培訓java培訓、期待與您交流! ———-

對於操作同一資源的不同行爲,該如何解決?

思路:因爲兩個線程操作的是同一資源,爲避免出現數據安全問題,必須實現同步,且設置的同步鎖對象一致,由於是兩個線程的兩種行爲,可以將資源類名.class作爲同步鎖傳入。

例子一:一邊輸入姓名和性別,一邊輸出。
定義資源類,包括姓名,性別,兩個屬性。

class Resource
{
     String name;
     String sex;
}

輸入類,實現Runnable接口,作用,輪流輸入兩個人的姓名和性別。

class Input implements Runnable
{   private Resource resource;
    public Input(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   int i=1;
        while(true)
        {
            if(i%2==0)
            {
                resource.name = "mack";
                resource.sex = "male";  
            }else
            {
                resource.name = "莉莉";
                resource.sex = "女女女女女女";
            }
            i++;
        }

    }
}

輸出類:實現Runnable接口,作用,輸出資源裏的姓名和性別。

class Output implements Runnable
{   private Resource resource;
    public Output(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   while(true)
        {
            System.out.println(resource.name+"....."+resource.sex);
        }

    }
}

在主函數中實例化並調用兩個線程

public class ThreadDemo
{
    public static void main(String args[])
    {
        Resource resource = new Resource();
        Input input = new Input(resource);
        Output out = new Output(resource);
        Thread t1 = new Thread(input);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

在未執行同步操作之前,輸出結果爲:
這裏寫圖片描述
發現出現了數據異常,並沒有同步輸出對應的名字和性別。因此需要使用同步。
同步之後代碼塊如下:

class Input implements Runnable
{   private Resource resource;
    public Input(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   int i=1;
        while(true)
        {
            synchronized(Resource.class)
            {
                if(i%2==0)
                {
                    resource.name = "mack";
                    resource.sex = "male";  
                }else
                {
                    resource.name = "莉莉";
                    resource.sex = "女女女女女女";
                }
                i++;
            }

        }

    }
}


class Output implements Runnable
{   private Resource resource;
    public Output(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   while(true)
        {
            synchronized(Resource.class)
            {
                System.out.println(resource.name+"....."+resource.sex);
            }
        }

    }
}

運行結果如下:
這裏寫圖片描述

應用同步解決了數據混亂的問題。
但是輸出方式卻是連着輸出,而不符合輪流輸出的效果。此時需要在輸入之後,讓輸入線程等待,讓輸出線程取出之後,喚醒輸入線程,同時輸出線程等待,如此循環,能到達輪流輸出的效果。

這就是重點:

線程間的通信,等待喚醒機制

(一)利用Synchronized同步來實現

修改上述代碼:
共享資源項:

class Resource
{
     String name;
     String sex;
     boolean flag = true;
}

輸入:

class Input implements Runnable
{   private Resource resource;
    public Input(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   int i=1;
        while(true)
        {
            synchronized(Resource.class)
            {   if(resource.flag)
                {
                    try{Resource.class.wait();}catch(Exception e){}
                }
                if(i%2==0)
                {
                    resource.name = "mack";
                    resource.sex = "male";  
                }else
                {
                    resource.name = "莉莉";
                    resource.sex = "女女女女女女";
                }
                i++;
                resource.flag = true;
                Resource.class.notify();
            }

        }

    }
}

輸出:

class Output implements Runnable
{   private Resource resource;
    public Output(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   while(true)
        {
            synchronized(Resource.class)
            {   
                if(!resource.flag)
                {
                    try{Resource.class.wait();}catch(Exception e){}
                }
                System.out.println(resource.name+"....."+resource.sex);
                resource.flag = false;
                Resource.class.notify();
            }
        }
    }
}

運行結果如下:
這裏寫圖片描述

現在實現了輪流輸出的效果。

上面講述的是一個輸入一個輸出,假如兩個生成者,兩個消費者,看看結果如何?

例子二:兩個生成者,兩個消費者,開啓四個線程。

class Resource
{
    private String name;
    private int count;
    boolean flag = true;
    public synchronized void set(String name)
    {   
        if(flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        this.name = name+"......."+count++;
        System.out.println(Thread.currentThread().getName()+".....生產者...."+this.name);
        flag = true ;
        this.notify();
    }
    public synchronized void out()
    {   
        if(!flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        System.out.println(Thread.currentThread().getName()+".....消費者...."+this.name);
        flag = false ;
        this.notify();
    }
}

class Producer implements Runnable
{
    private Resource resource;
    public Producer(Resource resource)
    {
        this.resource = resource;
    }
    public void run()
    {
        while(true)
        {
            resource.set("張三");
        }

    }
}
class Consumer implements Runnable
{
    private Resource resource;
    public Consumer(Resource resource)
    {
        this.resource = resource;
    }
    public void run()
    {
        while(true)
        {
            resource.out();
        }

    }
}

public class ProducerConsume
{
    public static void main(String args[]) throws Exception
    {
        Resource resource = new Resource();
        Producer pro = new Producer(resource);
        Consumer con = new Consumer(resource);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

運行結果如下:
這裏寫圖片描述
可以看出:出現了問題,當生產者生產出好幾個,但是消費者都未消費掉,是因爲當第一個生產者生產完,首先被喚醒的是第二個生產者,而它未經過判斷標誌位就開始生產,從而產生了這種情況。

修改辦法:
1)把Resource類中同步函數部分中,把if換成while(避免喚醒後不用判斷);
2)而用whlie可以實現循環判斷,四個線程會同時出現等待狀態,爲避免這種情況,修改notify爲notifyAll來實現。

class Resource
{
    private String name;
    private int count;
    boolean flag = true;
    public synchronized void set(String name)
    {   
        while(flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        this.name = name+"......."+count++;
        System.out.println(Thread.currentThread().getName()+".....生產者...."+this.name);
        flag = true ;
        this.notifyAll();
    }
    public synchronized void out()
    {   
        while(!flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        System.out.println(Thread.currentThread().getName()+".....消費者...."+this.name);
        flag = false ;
        this.notifyAll();
    }
}

運行結果如下:
這裏寫圖片描述

(二)JDK1.5之後Lock類

JDK1.5之後同步的新優點:
第一點:把隱性的synchronized同步鎖變成了可見的Lock代替,上鎖lock和解鎖unlock。
第二點:把Object(可操作wait,notify,notifyAll)同步鎖換成了Condition接口(await,signal,signalAll),此接口可以通過Lock類獲得newCondition。
第三點:最大的優點,一個鎖同時可以擁有許多個condition對象,可以有選擇性的喚醒個別線程。
備註:必須注意同步最後需要解鎖,此類最大的好處是隻定義一個鎖,就可以有好幾個condition,避免了synchronized定義多個鎖引起的同步嵌套,避免了死鎖的問題。實現了只喚醒對方線程的優點。
例子:改寫上述兩個生產和兩個消費過程的實例,

import java.util.concurrent.locks.*;

class Resource
{
    private String name;
    private int count;
    Lock lock = new ReentrantLock();
    Condition conditionPro = lock.newCondition();
    Condition conditionCon = lock.newCondition();
    boolean flag = true;
    public void set(String name) throws Exception
    {   
        lock.lock();
            try
            {   
                while(flag)
                conditionPro.await();
                this.name = name+"......."+count++;
                System.out.println(Thread.currentThread().getName()+".....生產者...."+this.name);
                flag = true ;
                conditionCon.signal();
            }finally
            {
                lock.unlock();
            }


    }
    public void out() throws Exception
    {   
        lock.lock();
            try
            {
                while(!flag)
                    conditionCon.await();
                System.out.println(Thread.currentThread().getName()+".....消費者...."+this.name);
                flag = false ;
                conditionPro.signal();
            }finally
            {
                lock.unlock();
            }

    }
}

運行結果如下:
這裏寫圖片描述

完美的解決了問題。比起synchronized更直觀,更清楚的讓人理解。

發佈了32 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章