線程間的通信--等待喚醒機制
例如:
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();
}
}
}
線程間的通信--等待喚醒機制