黑馬程序員java基礎之多線程

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

進程:是一個正在執行的程序。每個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫做一個控制單元

線程:就是進程中的一個獨立的控制單元。線程控制着進程的執行。

一個進程中 至少有一個線程

java VM啓動的時候會有一個進程java.exe,該進程中至少有一個線程負責java程序的執行,而且這個線程運行的代碼存在於main方法中,該線程稱爲主線程。

其實更多細節說明jvm啓動不止一個線程,還有負責垃圾回收機制的線程。

Thread類就是對線程這類事物的描述。

1.創建線程的第一種方法

繼承Thread類;

        複寫run方法;

覆蓋run方法的原因:Thread類定義了一個功能,用於存儲線程要運行的代碼,該存儲功能就是run方法

目的:將自定義的代碼存儲在run方法中,讓I線程運行

使用線程的start方法啓動線程,調用run方法

class Demo extends Thread	//繼承Thread類,創建線程
{
   public void run()	//複寫run方法
   {
       for(int x=0;x<10;x++)
       System.out.println("demo run----"+x);
    } 
}

class ThreadDemo
{
   public static void main(String[] args)
   {
       Demo d=new Demo();	//建立一個對象,同時也就創建了一個線程
       d.start();	//調用start()方法,啓動線程,調用run方法

       for(int x=0;x<10;x++)
       {
           System.out.println("Hello Word----------"+x);
        }
    }
}
運行結果:

發現運行結果每次都不一致,因爲多個線程都在獲取cpu的執行權,cpu執行到哪個線程,就運行哪個線程。在某一時刻,只能有一個程序在運行(多核除外)。

以上體現出了線程的一個特性,即隨機性,誰搶到誰運行,運行時間由cpu決定


練習:創建兩個現場,和主線程交替運行

class Demo extends Thread	//繼承Thread類
{
  
  Demo(String name)	//初始化構造函數,傳遞線程名稱
   {
      super(name);	//自定義線程名的方法,調用父類線程名
    }
   public void run() 
   {
    for(int x=0;x<60;x++)
           {
              System.out.println(Thread.currentThread().getName()+"T run---"+x);	//獲取當前線程名
            }   
    }
}
  

class ThreadTest
{
   public static void main(String[] args)
   {
       Demo d=new Demo("zhangsan");	//創建線程,並傳遞線程名
       Demo e=new Demo("lisi");
       d.start();
       e.start();
       for(int x=0;x<60;x++)
       {
            System.out.println("main run-------------"+x);
         }
    }
}


線程都有自己的默認名;格式:Thread-數字,數字從0開始

發現Thread構造方法初始化的時候就有名稱Thread(String name);線程Thread中有getName(),setName()方法來獲取線程名,所以只需要調用super(name)就可以獲取父類Thread來獲取線程名

static Thread currentThread():獲取當前線程對象

設置線程名稱:setName()或者構造函數

練習:簡單的買票程序:多個窗口同時買票

class Ticket implements Runnable//extends Thread	//繼承Thread類
{
   private <span style="color:#ff0000;">static</span> int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   public void run()	//複寫run方法
   {
      while(true)
       {
           if(tick>0)	//當還有票的時候
           System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
         }
    }
}

class TicketDemo
{
    public static void main(String[] args)
    {
          
	//創建線程
          Ticket t1=new Thread();
          Ticket t2=new Thread();
          Ticket t3=new Thread();
          Ticket t4=new Thread();

	//啓動線程,調用run方法
          t1.start();

          t2.start();

          t3.start();

          t4.start();
       }
}

但是使用靜態讓票數固定,會浪費資源,由此引入線程的第二種創建方法:

聲明實現Runnable接口;

覆蓋Runnable接口中的run方法;

將線程要運行的代碼放在該方法中

通過Thread類建立線程對象;

將Runnable接口的子類對象作爲實際參數傳入Thread類;

實現此步驟的原因:自定義的run方法所屬的對象是Runnable接口的子類對象,所以要讓線程去指定某個對象的run方法,就必須明確該run方法所屬對象

調用Thread類的start方法開啓線程,並調用Runnable接口的run方法

還是買票的例子:

class Ticket implements Runnable	//實現Runnable接口
{
   private int tick=100;	//初始化成員變量:票的總數
   public void run()	//複寫run方法
   {
      while(true)
       {
           if(tick>0)	//當還有票的時候
           System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
         }
    }
}

class TicketDemo
{
    public static void main(String[] args)
    {
         /*
 
	//創建線程
          Ticket t1=new Ticket();
          Ticket t2=new Ticket();
          Ticket t3=new Ticket();
          Ticket t4=new Ticket();

	//啓動線程,調用run方法
          t1.start();

          t2.start();

          t3.start();

          t4.start();

	*/

	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯,將實現Runnable接口的類對象作爲實際參數傳入線程對象中
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	Thread t3=new Thread(t);
	Thread t4=new Thread(t);

	//運行線程
	t1.start();
	t2.start();
	t3.start();
	t4.start();	
	
       }
}
運行結果:


線程也運行起來了

那麼繼承類與實現類有什麼區別呢?

實現方式避免了單繼承的侷限性。類可以在繼承的基礎上實現接口,功能得以拓展。在定義線程時,最好使用實現方式

線程代碼存在實現Runnable接口的子類的run方法中;而在繼承中,線程代碼存在於Thread子類的run方法中

在上面賣票的例子中通過傳入對象作爲參數,使各個線程運行的同時還可以保持票的總數不變

在售票的例子中,讓線程停頓10毫秒,發現結果出現了負數。多線程的運行出現了安全問題

class Ticket implements Runnable	//實現Runnable接口
{
   private static int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   public void run()	//複寫run方法
   {
      while(true)
       {
           if(tick>0)	//當還有票的時候
		try
		{
			Thread.sleep(10);
		}
		catch(Exception e)
		{
			
		}		
           System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
         }
    }
}

class TicketDemo1
{
    public static void main(String[] args)
    {
         /*
 
	//創建線程
          Ticket t1=new Ticket();
          Ticket t2=new Ticket();
          Ticket t3=new Ticket();
          Ticket t4=new Ticket();

	//啓動線程,調用run方法
          t1.start();

          t2.start();

          t3.start();

          t4.start();

	*/

	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	Thread t3=new Thread(t);
	Thread t4=new Thread(t);

	//運行線程
	t1.start();
	t2.start();
	t3.start();
	t4.start();
	
	
	
       }
}

問題的原因:當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒執行完,另一個線程參與進來執行,倒是共享數據的錯誤

解決辦法:對於多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與

對於次安全問題,使用同步代碼塊處理:

synchronized(對象)

{

需要被同步的代碼塊

}

之後票數就正常了

對象如同鎖,持有鎖的線程可以在同步中執行;沒有持有鎖的線程即使獲取cpu執行權,也進不去,因爲沒有持有鎖

同步的前提:1.必須要有兩個或兩個以上的線程;2.必須是多個線程使用同一個鎖

同步的好處:可以解決多線程的安全問題

弊端:多個線程需要判斷鎖,較爲消耗資源

練習:銀行金庫,兩個客戶分別存入300元,分3次,每次存100

如何找問題:明確那些代碼是多線程運行代碼;明確共享數據;明確多線程運行代碼中哪些語句是操作共享數據

引入同步函數:把同步作爲修飾符傳給函數

class Bank	//對銀行進行描述
{
	Object obj=new Object();
	private int sum;	//引用成員變量,銀行金庫錢的總數
	public synchronized void add(int n)	//存入錢之後,金庫內錢增加,計算總和,同步函數
	{	
		//synchronized(obj)
		//{
			sum=sum+n;
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
		
			}
			System.out.println("sum="+sum);
		//}
	}
}

class Cus implements Runnable	//實現Runnable接口
{
	private Bank b=new Bank();	//引用Bank對象作爲成員變量
	public void run()	//複寫run方法
	{
		for(int x=0;x<3;x++)	//分爲3次存錢
		{
			b.add(100);	//銀行金庫總存款增加
		}
	}
}

class BankDemo
{
	public static void main(String[] args)
	{
		Cus c=new Cus();	//新建實現Runnable接口的類對象
		//創建線程對象,並將實現Runnable接口的類對象作爲參數傳入
		Thread t1=new Thread(c);
		Thread t2=new Thread(c);

		//啓動線程,調用Runnable接口子類的run方法
		t1.start();
		t2.start();
	}
}


那麼同步函數用的是哪個鎖呢?函數需要被對象調用,那麼函數都有一個所屬對象調用,即this.所以同步函數使用的鎖是this

接下來就買票的例子進行驗證

class Ticket implements Runnable	//實現Runnable接口
{
   private static int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   Object obj=new Object();
   boolean flag=true;
   public void run()	//複寫run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(obj)	//傳入對象設爲上帝類Object
	   {	//這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
          	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public synchronized void show()<span style="white-space:pre">	</span>//同步函數調用的是使用該方法的對象的鎖,即this
   {
	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
    }
}

class TicketDemo2
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//運行線程
	t1.start();

	//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
	try
	{
		Thread.sleep(10);
	}
	catch(Exception e)
	{}

	t.flag=false;	//將標誌更改,使得t2運行同步函數
	t2.start();
	
       }
}

發現結果中有出售0號票的


原因是共享數據用的不是同一個鎖,第一個線程用的是obj的鎖,第二個線程用的是this鎖

將代碼塊的鎖也改爲this

class Ticket implements Runnable	//實現Runnable接口
{
   private static int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   Object obj=new Object();
   boolean flag=true;
   public void run()	//複寫run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(<span style="color:#ff0000;">this</span>)	
	   {	//這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
          	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public synchronized void show()
   {
	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
    }
}

class TicketDemo2
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//運行線程
	t1.start();

	//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
	try
	{
		Thread.sleep(10);
	}
	catch(Exception e)
	{}

	t.flag=false;	//將標誌更改,使得t2運行同步函數
	t2.start();
	
       }
}


同步函數和同步代碼塊都使用的是this鎖,如此就沒有0號票了


靜態同步函數:如果同步函數被靜態修飾後,使用的鎖是什麼呢?

class Ticket implements Runnable	//實現Runnable接口
{
   private static int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   Object obj=new Object();
   boolean flag=true;
   public void run()	//複寫run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(<span style="color:#ff0000;">this</span>)	
	   {	//這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
          	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public static synchronized void show()
   {
	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
    }
}

class TicketDemo3
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//運行線程
	t1.start();

	//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
	try
	{
		Thread.sleep(1);
	}
	catch(Exception e)
	{}

	t.flag=false;	//將標誌更改,使得t2運行同步函數
	t2.start();
	
       }
}

運行結果有0號票,說明用的不是同一個鎖,同步函數的鎖不在是this了,因爲靜態中不存在this

靜態在內存是,內存中沒有本類對象,但是一定有該類對象對應的字節碼文件對象

類名.class,該對象的類型是Class

class Ticket implements Runnable	//實現Runnable接口
{
   private static int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   Object obj=new Object();
   boolean flag=true;
   public void run()	//複寫run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(<span style="color:#ff0000;">Ticket.class</span>)	
	   {	//這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
          	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public static synchronized void show()
   {
	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
    }
}

class TicketDemo3
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//運行線程
	t1.start();

	//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
	try
	{
		Thread.sleep(1);
	}
	catch(Exception e)
	{}

	t.flag=false;	//將標誌更改,使得t2運行同步函數
	t2.start();
	
       }
}


發現程序又安全了,所以靜態的同步方法使用的鎖是該方法所在類的字節碼文件對象,類名.class


單例設計模式:懶漢式的同步

先複習下餓漢式

class Single
{
	private static final Single s=new Single();<span style="white-space:pre">	</span>//一開始就初始化對象值
	private Single()
	{}
	public static  Single getInstance()
	{
		return s;
	}
}

//懶漢式
class Single
{
	private static Single s=null;
	private Single()	
	{}
	public static Single getInstance()
	{
		if(s==null)
			s=new Single();
		return s;
	}
}

對於懶漢式,如果同時有多個線程在調用該類,就會出現安全問題,一個在判斷,一個在賦值,不能保證對象的唯一性,所以引入同步,

class Single
{
	private static Single s=null;	//引入成員變量
	private Single()	
	{}
	public static Single getInstance()	//創建獲取實例對象的方法
	{
		if(s==null)	//使用雙重判斷,提高效率
		{	
		synchronized(Single.class)
		if(s==null)
			s=new Single();
		return s;
		}
	}
}
使用雙重判斷,卻比較麻煩,所以平常使用最好使用餓漢式,餓漢式只有一句話,不存在同步問題


死鎖:通常情況下是同步中嵌套同步,而鎖不同

class Ticket implements Runnable	//實現Runnable接口
{
   private static int tick=100;	//初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
   Object obj=new Object();
   boolean flag=true;
   public void run()	//複寫run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(obj)	//同步代碼塊中調用同步函數
	   {	
          	show();
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public synchronized void show()	//同步函數中包含同步代碼塊,調用的是this鎖
   {
	synchronized(obj)
	{
	 if(tick>0)	//當還有票的時候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
		}
	}
    }
}

class DeadLockDemo2
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//創建一個Ticket對象

	//創建線程,並將線程與run()方法相關聯
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//運行線程
	t1.start();

	//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
	try
	{
		Thread.sleep(1);
	}
	catch(Exception e)
	{}

	t.flag=false;	//將標誌更改,使得t2運行同步函數
	t2.start();
	
       }
}


發現沒運行一會兒變沒有了,發生了死鎖


寫出一個死鎖程序

class Test implements Runnable	//實現Runnable接口
{
   private boolean flag;
   Test(boolean flag)
   {
      this.flag=flag;
    }
    public void run()	//複寫run方法
    {
      
        if(flag)
        {
          while(true)
          {
           synchronized(MyLock.locka)	//locka鎖嵌套lockb鎖
           {
             
               System.out.println("if locka");
               synchronized(MyLock.lockb)
               {
                  System.out.println("if lockb");
                }
            }
        }
       }
        else
        { 
         while(true)
         {
          synchronized(MyLock.lockb)	//lockb中嵌套locka鎖
           {
               System.out.println("else lockb");
               synchronized(MyLock.locka)
               {
                   System.out.println("else locka");
                }
            }
         }
       }
     }
}


class MyLock	//創建一個鎖對象
{
   static Object locka=new Object();
   static Object lockb=new Object();
}

class DeadLockDemo
{
   public static void main(String[] args)
   {
	//創建線程,傳入實現Runnable接口的類作爲參數
      Thread t1=new Thread(new Test(true));
      Thread t2=new Thread(new Test(false));
	//啓動線程
      t1.start();
      t2.start();
   }
}

只循環了一次便終止了,發生了死鎖

線程間通訊

建立一個共享資源,不斷交替傳入兩個姓名和性別,然後打印出來

/*
練習:不斷的存入兩個人名,不斷的輸出打印
*/
class  InputOutputDemo1
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//建立資源對象
		//創建線程第二步,創建Runnable接口的子類對象
		Input in=new Input(r);	//建立輸入對象,並將資源對象作爲參數傳入
		Output out=new Output(r);	//建立輸出對象,並將新建的資源對象傳入
		//創建線程第三步,新建線程,並將實現Runnable的子類對象傳入
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();


	}
}
class Res	//建立共有資源類
{
	//包含姓名和性別
	 String name;	
	 String  sex;
}
class Input implements Runnable		//存入線程,將姓名和性別存入資源中
{
	private Res r;	//創建資源類型的成員變量
	Input(Res r)	//構造函數初始化時就獲取資源對象
	{
		this.r=r;
	}
	public void run()	//複寫run方法
	{
		int x=0;
		while(true)
		{
			synchronized(r)<span style="white-space:pre">		</span>//加入同步,避免第一個性別還沒打印,第二個性別就先打印了,此處尋找同一個鎖,發現r就是共享對象
			{
				if(x==0)
				{
					r.name="mike";
					r.sex="男";
				}
				else
				{
					r.name="lili";
					r.sex="女女女女女女女女女";
				}
				x=(x+1)%2;	//通過x的值的變換,來切換兩個名字互相切換
			}
		}
	}
}

class Output implements Runnable
{
	private Res r;	//與傳入線程類似,創建資源型成員變量
	Output(Res r)	//初始化構造函數的時候獲取資源對象
	{
		this.r=r;
	}
	public void run()	//複寫run方法
	{
		while(true)
		{
			synchronized(r)<span style="white-space:pre">		</span><span style="font-family: Arial, Helvetica, sans-serif;">//加入同步,避免第一個性別還沒打印,第二個性別就先打印了,此處尋找同一個鎖,發現r就是共享對象</span><span style="white-space:pre">
</span>
			{
				System.out.println(r.name+"=="+r.sex);
			}
		}
	}
}
注意,在上面中,對姓名和性別賦值,打印都在不斷操作共同數據,所以在輸出語句中也要加入鎖

此時雖然不會有性別和姓名錯亂,但是希望的結果是兩個姓名和年齡交替打印,而這裏可能輸入線程出了同步之後,沒有被輸出縣城搶到執行權,繼續被輸入線程搶到執行權,然後繼續覆蓋之前的姓名,不斷重複打印一個姓名和性別

此處引入等待喚醒機制

/*
練習:不斷的存入兩個人名,不斷的輸出打印
*/
class  InputOutputDemo1
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//建立資源對象
		//創建線程第二步,創建Runnable接口的子類對象
		Input in=new Input(r);	//建立輸入對象,並將資源對象作爲參數傳入
		Output out=new Output(r);	//建立輸出對象,並將新建的資源對象傳入
		//創建線程第三步,新建線程,並將實現Runnable的子類對象傳入
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();


	}
}
class Res	//建立共有資源類
{
	//包含姓名和性別
	 String name;	
	 String  sex;
	 boolean flag=false;	//添加一個標誌位,來維持兩個線程分別進行
}
class Input implements Runnable		//存入線程,將姓名和性別存入資源中
{
	private Res r;	//創建資源類型的成員變量
	Input(Res r)	//構造函數初始化時就獲取資源對象
	{
		this.r=r;
	}
	public void run()	//複寫run方法
	{
		int x=0;
		while(true)
		{
			synchronized(r)
			{
				if(r.flag)
				{
					try{
						r.wait();	//如果標誌位爲真,就代表資源中姓名和年齡有值,讓線程進入等待狀態,讓輸出線程獲得執行權
						}
					catch(Exception e){}
				}
				if(x==0)
				{
					r.name="mike";
					r.sex="男";
				}
				else
				{
					r.name="lili";
					r.sex="女女女女女女女女女";
				}
				x=(x+1)%2;	//通過x的值的變換,來切換兩個名字互相切換
				r.flag=true;	//賦值之後,將標誌位修改爲真
				r.notify();	//喚醒等待的線程,兩個線程都有執行權,如果此線程搶到執行權,會返回上面判斷,然後就會等待
			}
		}
	}
}

class Output implements Runnable
{
	private Res r;	//與傳入線程類似,創建資源型成員變量
	Output(Res r)	//初始化構造函數的時候獲取資源對象
	{
		this.r=r;
	}
	public void run()	//複寫run方法
	{
		while(true)
		{
			synchronized(r)
			{				
				if(!r.flag)		//如果標誌位爲假,則是姓名性別的值已經打印過了爲空了
				{
					try{
					r.wait();		//就讓打印線程等待,輸入線程運行
						}
					catch(Exception e){}
				}
				System.out.println(r.name+"=="+r.sex);
				r.flag=false;	//打印完之後,將標誌位的值修改,
				r.notify();	//將等待的線程喚醒,即喚醒輸入線程;如果輸出線程再搶到執行權,就會再回到上面判斷,發現爲假,就會等待,然後只有輸入線程執行了

			}
		}
	}
}
wait();notify();notifyAll() 都使用在同步中,因爲要對持有監視器的線程操作,所以都使用在同步中,因爲只有監視器中才有鎖   在上面,因爲線程存在嵌套,所以使用wait,notify方法的時候需要標明是調用對象鎖r,即r.wart,r.notify;

上述方法都在Object中,因爲等待和喚醒必須是同一個鎖,而鎖可以是任意一個對象,所以是定義在Object中的

下面可以對代碼進行優化

/*
練習:不斷的存入兩個人名,不斷的輸出打印
*/
class  InputOutputDemo1
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//建立資源對象
		//創建線程第二步,創建Runnable接口的子類對象
		Input in=new Input(r);	//建立輸入對象,並將資源對象作爲參數傳入
		Output out=new Output(r);	//建立輸出對象,並將新建的資源對象傳入
		//創建線程第三步,新建線程,並將實現Runnable的子類對象傳入
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();


	}
}
class Res	//建立共有資源類
{
	//包含姓名和性別
	 private String name;	
	 private String  sex;
	 private boolean flag=false;	//添加一個標誌位,來維持兩個線程分別進行
	 public synchronized void set(String name,String sex)
	{
		 if(flag)
		{
			try{
				this.wait();	//如果標誌位爲真,就代表資源中姓名和年齡有值,讓線程進入等待狀態,讓輸出線程獲得執行權
				}
			catch(Exception e){}
		}
	 this.name=name;
	 this.sex=sex;
	 flag=true;
	 this.notify();<span style="white-space:pre">	</span>//喚醒鎖對象監視的等待線程,此處即輸出線程
	 }
	 public synchronized void printout()
	{
		 if(!flag)
		{
				try{
					this.wait();	//如果標誌位爲真,就代表資源中姓名和年齡有值,讓線程進入等待狀態,讓輸出線程獲得執行權
					}
				catch(Exception e){}
		}
	 System.out.println(this.name+"=="+this.sex);
	 flag=false;
	 this.notify();<span style="white-space:pre">	</span>//喚醒鎖對象監視的等待的線程,此處即喚醒輸入線程
	 }
}
class Input implements Runnable		//存入線程,將姓名和性別存入資源中
{
	private Res r;	//創建資源類型的成員變量
	Input(Res r)	//構造函數初始化時就獲取資源對象
	{
		this.r=r;
	}
	public void run()	//複寫run方法
	{
		int x=0;
		while(true)
		{
				if(x==0)
					r.set("mike","男");
				else
					r.set("麗麗","女女女女女女女女女");
				x=(x+1)%2;	//通過x的值的變換,來切換兩個名字互相切換
		}
	}
}

class Output implements Runnable
{
	private Res r;	//與傳入線程類似,創建資源型成員變量
	Output(Res r)	//初始化構造函數的時候獲取資源對象
	{
		this.r=r;
	}
	public void run()	//複寫run方法
	{
		while(true)
		{				
			r.printout();
		}
	}
}

練習:多個生產者和消費者同時運行,但要保持生產一個產品就要消費一個產品

class BCTest 
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//創建資源對象
		//創建生產消費者對象,並把資源對象作爲參數傳入
		Pro p=new Pro(r);
		Consumersumersumer c=new Consumersumersumer(r);
		//創建線程
		Thread t1=new Thread(p);
		Thread t2=new Thread(p);
		Thread t3=new Thread(c);
		Thread t4=new Thread(c);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Res
{
	private String name;	//創建產品名稱變量
	private int count=1;	//產品編號變量
	private boolean flag;	//標誌位
	public synchronized void set(String name)	//構造生產者線程的調用方法
	{
		while(flag)	//使用whil循環,使得被喚醒的線程每次都要先判斷標誌,避免生產一個還沒消費就被覆蓋掉了
		{
			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();	//喚醒處於等待狀態的所有線程
	}
}
class Pro implements Runnable	//創建生產者類
{
	private Res r;	//創建私有變量爲資源類型的
	Pro(Res r)	//初始化構造函數
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
			r.set("商品");	//調用生產者方法
	}
}
class Consumersumersumer implements Runnable	//創建消費者類
{
	private Res r;	//創建私有資源類型的變量
	Consumersumersumer(Res r)	//初始化構造函數獲取資源對象
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
			r.out();	//調用消費者方法
	}
}
JDK5.0之後,synchronized 被Lock(接口)替代;調用其方法newCondition()可以新建一個Condition對象,包含notify,wait,notifyAll方法

並且可以建立多個Condition對象,對於上述例子,可以新建兩個分別代表生產者和消費者對應的Condition對象

import java.util.concurrent.locks.*;
class CPTest 
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//創建資源對象
		//創建生產消費者對象,並把資源對象作爲參數傳入
		Pro p=new Pro(r);
		Consumersumersumer c=new Consumersumersumer(r);
		//創建線程
		Thread t1=new Thread(p);
		Thread t2=new Thread(p);
		Thread t3=new Thread(c);
		Thread t4=new Thread(c);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Res
{
	private String name;	//創建產品名稱變量
	private int count=1;	//產品編號變量
	private boolean flag;	//標誌位
	private Lock lock=new ReentrantLock();	//建立鎖的對象,取代了舊版中的同步
	private Condition cond_con=lock.newCondition();		//condition中封裝了wait,notify,notifyAll方法,所以建立此對象可以調用其中的方法
	private Condition cond_pro=lock.newCondition();		//建立消費者對應的Condition對象

	public void set(String name) throws InterruptedException	//構造生產者線程的調用方法
	{
		lock.lock();	//加上鎖
		try
		{
			while(flag)	//使用whil循環,使得被喚醒的線程每次都要先判斷標誌,避免生產一個還沒消費就被覆蓋掉了
			{
				
				cond_con.await();	//如果標誌位爲真,則讓生產者等待,讓消費者消費
			}
			this.name=name+"=="+count++;	//產品一有名稱就自帶編號

			System.out.println(Thread.currentThread().getName()+"生產者=========="+this.name);
			flag=true;	//修改標誌位
			cond_pro.signal();		//喚醒等待的線程
		}
		finally
		{
			lock.unlock();	//解鎖
		}
		
	}
	public void out() throws InterruptedException	//構造消費者線程的調用方法
	{
		lock.lock();
		try{
		while(!flag)
		{
				cond_pro.await();
		}
		System.out.println(Thread.currentThread().getName()+"消費者"+this.name);
		flag=false;	//打印完成後修改標誌位
		cond_con.signal();		//喚醒等待線程
		}
		finally
		{
			lock.unlock();	//解除鎖,釋放資源
		}
	}
}
class Pro implements Runnable	//創建生產者類
{
	private Res r;	//創建私有變量爲資源類型的
	Pro(Res r)	//初始化構造函數
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
			try
			{
				r.set("商品");	//調用生產者方法
			}
			catch (InterruptedException e)
			{
			}
		
	}
}
class Consumersumersumer implements Runnable	//創建消費者類
{
	private Res r;	//創建私有資源類型的變量
	Consumersumersumer(Res r)	//初始化構造函數獲取資源對象
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
		try
		{
			r.out();	//調用消費者方法
		}
		catch (InterruptedException e)
		{
		}
			
	}
}
同時,新版本之後,要想停止線程只有一種方法,就是結束run方法

在開啓多線程運行的時候,通常會在運行代碼中使用循環結構,只要控制住循環結構就可以終止線程





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