——- 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更直觀,更清楚的讓人理解。