1. 程式人生 > >線程間的通信--等待喚醒機制

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

return sta 程序 private 實現 tac 如何解決 因此 循環

1.多個線程操作相同的資源,但是操作動作不同,所以存在安全問題
例如:

public class Test {
    public static void main(String[] args) {
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);

        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}
/**
 * 兩個線程共同的資源
 * @author WangShuang
 *
 */
class Resource{
    private String name;
    private String sex;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}
/**
 * 存資源的線程
 * @author WangShuang
 *
 */
class Input implements Runnable {
     private Resource resource;
    public Input(Resource resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        int x =0;
        while(true){
            synchronized (new Object()) {
                if(x==0){
                    resource.setName("張三");
                    resource.setSex("男");
                }else{
                    resource.setName("lili");
                    resource.setSex("女女女");
                }
                x=(x+1)%2;
            }
        }
    }
}
/**
 * 
 * @author 取資源的線程
 *
 */
class Output implements  Runnable {
    private Resource resource;
    public Output(Resource resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        while(true){
            synchronized (new Object()) {
                System.out.println(resource.getName()+"..."+resource.getSex());
            }

        }
    }
}

運行結果:
張三...男
張三...女女女
張三...男
張三...男
張三...女女女
lili...男

發生上述問題的原因:當output取線程時,output還沒取完,例如只get到了name張三,cpu的執行權就被input奪走,執行了兩個setlili 女,當output再次取線程是sex已經變成了女,所以出

現了 張三。。。女的現象
添加了同步也不管用,那麽就應該思考同步的前提 是不是兩個或兩個以上的線程操作共享資源,是不是同一個鎖對象,很明顯沒有滿足

2.現在更改代碼用同一個鎖

public class Test {
    public static void main(String[] args) {
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);

        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}
/**
 * 兩個線程共同的資源
 * @author WangShuang
 *
 */
class Resource{
    private String name;
    private String sex;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}
/**
 * 存資源的線程
 * @author WangShuang
 *
 */
class Input implements Runnable {
     private Resource resource;
    public Input(Resource resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        int x =0;
        while(true){
            synchronized (resource) {
                if(x==0){
                    resource.setName("張三");
                    resource.setSex("男");
                }else{
                    resource.setName("lili");
                    resource.setSex("女女女");
                }
                x=(x+1)%2;
            }
        }
    }
}
/**
 * 
 * @author 取資源的線程
 *
 */
class Output implements  Runnable {
    private Resource resource;
    public Output(Resource resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        while(true){
            synchronized (resource) {
                System.out.println(resource.getName()+"..."+resource.getSex());
            }

        }
    }
}

運行結果
張三...男
張三...男
張三...男
張三...男
張三...男
張三...男
張三...男
張三...男
張三...男
張三...男
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
雖然已經解決了張三。。。女和lili...男的問題,但是出現的大片的張三...男和大片的lili...女,這是為什麽呢

當tin線程獲得cpu的執行權後,執行了setName和setSext方法,cpu的執行權依然在tin線程手裏,又一次執行了setName和setSext方法,覆蓋了之前的setName和setSex方法,之後的同理。。。。。,那麽又該如何實現存一個取一個呢?java的多線程中存在等待喚醒機制,代碼如下

public class Test1 {
    public static void main(String[] args) {
        Resource1 r = new Resource1();
        Input1 input = new Input1(r);
        Output1 output = new Output1(r);
        new Thread(input).start();
        new Thread(input).start();
        new Thread(output).start();
        new Thread(output).start();

    }
}
/**
 * 兩個線程共同的資源
 * @author WangShuang
 *
 */
class Resource1{
    private String name;
    private String sex;
    private int count;
    private boolean flag;//添加一個標記用來表示Resource中的資源是否為空(Input以後代表存入不為空,Output以後代表取出為空)
    public  String getOutput() {
        System.out.println(Thread.currentThread().getName()+"消費了一個"+sex+"---------------"+name);
        return name+"---"+sex;
    }
    public  void setInput(String name,String sex) {
        this.name = name+count++;
        this.sex = sex;
        System.out.println(Thread.currentThread().getName()+"生產了一個"+this.sex+"---"+this.name);
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
/**
 * 存資源的線程
 * @author WangShuang
 *
 */
class Input1 implements Runnable {
     private Resource1 resource;
    public Input1(Resource1 resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        int x =0;
        while(true){
            synchronized (resource) {
                if(resource.isFlag()){//如果flag是真,代碼資源庫中的資源還沒有被取走,此時該線程應該放棄cpu的執行權,並把另一個線程叫醒
                    try {resource.wait();} catch (InterruptedException e) {e.printStackTrace();}
                }
                try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//此處的線程等待是為了方便看運行結果
                if(x==0){
                    resource.setInput("張三","男");
                }else{
                    resource.setInput("lili","女");
                }
                x=(x+1)%2;
                resource.setFlag(true);
                resource.notify();
            }
        }
    }
}
/**
 * 
 * @author 取資源的線程
 *
 */
class Output1 implements  Runnable {
    private Resource1 resource;
    public Output1(Resource1 resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        while(true){
            synchronized (resource) {
                if(!resource.isFlag()){//如果flag是真,代碼資源庫中的資源還沒有被取走,此時該線程應該放棄cpu的執行權,並把另一個線程叫醒
                    try {resource.wait();} catch (InterruptedException e) {e.printStackTrace();}
                }
                try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//此處的線程等待是為了方便看運行結果
                resource.getOutput();
                resource.setFlag(false);
                resource.notify();
            }
        }
    }
}

運行結果如下:
Thread-0生產了一個男---張三0
Thread-3消費了一個男---------------張三0
Thread-1生產了一個男---張三1
Thread-2消費了一個男---------------張三1
Thread-1生產了一個女---lili2
Thread-3消費了一個女---------------lili2
Thread-0生產了一個女---lili3
Thread-3消費了一個女---------------lili3
Thread-0生產了一個男---張三4
Thread-3消費了一個男---------------張三4
Thread-1生產了一個男---張三5
Thread-2消費了一個男---------------張三5
Thread-1生產了一個女---lili6
Thread-3消費了一個女---------------lili6
Thread-0生產了一個女---lili7
Thread-3消費了一個女---------------lili7
Thread-0生產了一個男---張三8
Thread-3消費了一個男---------------張三8
Thread-0生產了一個女---lili9
Thread-3消費了一個女---------------lili9
Thread-0生產了一個男---張三10
Thread-2消費了一個男---------------張三10
Thread-0生產了一個女---lili11
Thread-1生產了一個男---張三12
Thread-0生產了一個男---張三13
Thread-1生產了一個女---lili14
Thread-0生產了一個女---lili15
Thread-2消費了一個女---------------lili15
Thread-3消費了一個女---------------lili15
Thread-1生產了一個男---張三16
Thread-2消費了一個男---------------張三16
Thread-3消費了一個男---------------張三16

Thread-1生產了一個女---lili17

出現了生產一個,消費2個的狀態,那麽這是為什麽呢?

首先明白一件事:不具備執行資格的線程存在於內存中的線程池中,喚醒的線程的順序,是誰先被等待,誰先被喚醒

當Thread-0獲得cpu的執行權時,先判斷資源是否為空,是開始生產資源,然後把判斷資源是否為空的標記flag,設置為true,喚醒一個線程,Thread-0繼續擁有cpu的執行權,先判斷資源是否為空,不是空,wait()等待。然後Thread-1獲得cpu的執行權,先判斷資源是否為空,不是空,wait()等待。然後Thread-2獲得cpu的執行權,先判斷資源是否為空,不是空,取出資源,然後flag為false,喚醒一個線程,然後Thread-3開始執行,先判斷資源是否為空,是空,wait()等待,然後Thread-0獲得cpu的執行權,上次Thread-0失去cpu的執行權時是通過resource.wait(),接著繼續運行,生產資源,設置flag為true,喚醒一個線程,Thread-1獲得cpu的執行權,上次Thread-1失去cpu的執行權時是通過resource.wait(),接著繼續運行,生產資源,因此產生了生產兩個資源的現象,設置flag為true,喚醒一個線程,然後Thread-2獲得cpu的執行權,因為上次失去cpu的執行權是在resource.wait(),所以繼續執行,不用判斷資源是否為空,取出資源,然後flag為false,喚醒一個線程,然後Thread-3開始執行,因為上次失去cpu的執行權是在resource.wait(),所以繼續執行,不用判斷資源是否為空,取出資源,因此產生了消費兩個相同資源的現象
註意現在的線程順序是我自己本人想象出來的cpu的執行順,cpu的執行順序是隨機的,所以各種現象都能解釋的通,例如生產了兩個,消費了一個,生產一個消費兩個,生產兩個不一樣的,消費2個一樣的(上述解釋)等等,那麽這個問題該如何解決呢?

產生上述問題的原因是因為,沒有判斷標記,那麽
把判斷資源是否為空的地方if(resource.getFlag())改成while(resource.getFlag()),這樣就可以循環的判斷標記了,當我們再次運行程序時,會發現Thread-0,Thread-1,Thread-2,Thread-3,全部等待的狀態,產生這種現象的原因,自己按照代碼的執行順序自己理一遍就明白了,那麽又該怎麽辦呢?

喚醒時,不要只喚醒一個線程,全部都喚醒好了notifyAll()

下面的代碼是最後的並且簡化後的代碼

public class Test {
    public static void main(String[] args) {
        Resource r = new Resource();
        Input input = new Input(r);
        Output output = new Output(r);
        new Thread(input).start();
        new Thread(input).start();
        new Thread(output).start();
        new Thread(output).start();

    }
}
/**
 * 兩個線程共同的資源
 * @author WangShuang
 *
 */
class Resource{
    private String name;
    private String sex;
    private int count;
    private boolean flag;//添加一個標記用來表示Resource中的資源是否為空(Input以後代表存入不為空,Output以後代表取出為空)
    public synchronized String getOutput() {
        while(flag){//如果flag是真,代碼資源庫中的資源還沒有被取走,此時該線程應該放棄cpu的執行權,並把另一個線程叫醒
            try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}
        }
        System.out.println(Thread.currentThread().getName()+"消費了一個"+sex+"---------------"+name);
        flag=true;
        this.notifyAll();
        return name+"---"+sex;

    }
    public synchronized void setInput(String name,String sex) {
        while(!flag){//如果flag是假,代碼資源庫中的資源已經被取走,此時該線程應該放棄cpu的執行權,並把另一個線程叫醒
            try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}
        }
        this.name = name+count++;
        this.sex = sex;
        System.out.println(Thread.currentThread().getName()+"生產了一個"+this.sex+"---"+this.name);
        flag=false;
        this.notifyAll();
    }

}
/**
 * 存資源的線程
 * @author WangShuang
 *
 */
class Input implements Runnable {
     private Resource resource;
    public Input(Resource resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        int x =0;
        while(true){
                if(x==0){
                    resource.setInput("張三","男");
                }else{
                    resource.setInput("lili","女");
                }
                x=(x+1)%2;
        }
    }
}
/**
 * 
 * @author 取資源的線程
 *
 */
class Output implements  Runnable {
    private Resource resource;
    public Output(Resource resource) {
        this.resource=resource;
    }
    @Override
    public void run() {
        while(true){
                resource.getOutput();
        }
    }
}

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