java基礎_07_多線程

多線程

線程是程序運行的基本執行單元,一個進程中可以包含多個線程,這些線程共享這個進程中的內存空間。但是進程和進程之間是不共享內存的,都有自己的獨立的運行空間。

 

建立線程的兩種方法

1,一種方法是類去繼承 Thread 類,其實是Thread自己實現了Runnable

2,用接口的方法,去實現 Runnable (用這個比較好)

   創建步驟:

1,定義自己的類實現Runnable接口

2,覆蓋Runnable接口中的run方法

3,通過Thread類建立線程對象

4,將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數

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

 

   例子:賣票窗口,3個窗口賣100張票

 

兩種方法的區別?

java 是單繼承 如果繼承 Thread 則不能在繼承其它的類 ,實現接口的話還可以繼承其它類,接口可以多實現的

 

多線程會出現安全問題

一:解決方法是加synchronized ,單線程不存在這個問題

     好處:解決了安全問題    

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

1,
    synchronized()
    {
    	共享代碼塊
    }																							   
     例:
   	 Object obj=new Object();
   	 public void run()
   	 {
   	 	while(true)
       	{
    			synchronized(obj)//加鎖安全 這個鎖的是obj 
    			{
    				if(tick>0)    //這句在多線程來說是有安全問題的,如果兩個tick=1了,
 					      //多個進程同時進到這裏來執行,就會出現問題
    				System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
    			}
    		}
    }

    2,可以把共享代碼封裝在一個方法中,在調用其方法就行, 在方法上加上synchronized
     例:
      public synchronized void run() //加鎖 這個鎖的是this 
      {
      	while(true)
      	{
      		if(tick>0)  		
             		System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
      	}
      }
     或:
       public  void  method() 
       {	
           synchronized(this)  //相當於鎖的是 method() 方法
           {
         	共享代碼塊
    	      }	
       }
    

   3,或直接把synchronized 放在方法上,鎖的是this,如果方法被static靜態了,那麼鎖的就不是this了。(靜態中也不可能有this),這個時候鎖的是所在類的字節碼文件對象。<類名.class>                                        
      不同鎖的建立:
       建立鎖:
    class Loak
    {
    	static Object loak1=new Object();
    	static Object loak2=new Object();//兩種鎖
    }
    
  	 加入鎖:
    synchronized(Loak.loak1)
    {
    		
    }

二:JDK1.5中提供了多線程升級解決方案

將同步synchronized替換成現實lock操作

要爲特定 Lock 實例獲得 Condition 實例,使用其 newCondition() 方法。

Condition 實例實質上被綁定到一個鎖上。

Condition 將 Object 監視器方法(waitnotify 和 notifyAll)分解成截然不同的對象,

以便通過將這些對象與任意 Lock 實現組合使用

 

採用 lock 鎖來鎖定代碼

   final Condition nl  = lock.newCondition(); 

   final Condition n2 = lock.newCondition(); 

 

用 n1.signal   n2.signal 來喚醒一個指定等待線程

用 n1.signalAll    n2.signalAll  來喚醒所有指定等待線程           

       例:

    class ziyuan //資源
    {
	
		//定義鎖	
		private Lock lock = new ReentrantLock();  
	
		private Condition c1 = lock.newCondition();
		private Condition c2 = lock.newCondition();

   		 public void set(String name) throws InterruptedException
		{
			lock.lock();   代碼上鎖
			try
			{
				while(!flg)
				{
			   		 c1.await();    // p1  等待
				}
			
				flg = false;
				c2.signal();	// v2	喚醒
			}
			finally 
			{
				lock.unlock();  最後一定要解鎖
			}
	}

      如果在一個類中有多處使用加鎖,要處理同一共享數據時。要加鎖同一個。不然就要出錯

 

注意: 使用這個不要出現 死鎖 ,當類中存在多個不同的鎖時,而這些鎖存在交叉,那麼就可能會出現死鎖

如:

1,鎖A--11

      {B--22}

2,鎖B--33

      {A--44} 

像上面這樣出現交叉的鎖。如11 33 同時執行後 要運行22 44就鎖住了。22被下面B鎖了,44又被上面11鎖住了。那麼就執行不下去了

 

怎麼讓線程停下來呢?

stop已過時,只有一種方法,讓 run 方法停止。

開啓多線程運行,運行代碼通常是循環結構,只要控制住循環,就可以讓run 方法結束,也就是線程結束。 

用 while 循環來判斷標識。

原因:讓被喚醒的線程再一次判斷標記

例:

    class StopThread implements Runnable
    {
    	private boolean flag = true; //標記,用於控制線程結束
    	public void run()
    	{
    		while(flag)
    		{
    			System.out.println(Thread.currentThread().getName()+"..........run");	
    		}
    	
    	}
    	public void changeFlag() //更改標記
    	{
    		flag = false;
    	}
    }
    class  ThreadStopclass
    {
    	public static void main(String[] args) 
    	{
    		StopThread st = new StopThread();
    
    		Thread t1 = new Thread(st);
    		Thread t2 = new Thread(st);
    
    		t1.start();
    		t2.start();
    
    		int num = 0;
    		while(true)
    		{
    			if(num++==60)
    			{
    				st.changeFlag(); //修改標記讓線程停下
    				break;
    			}				
    			System.out.println(Thread.currentThread().getName()+"..........");
    		}
    		System.out.println("主函數運行完。。。");
    	}
    }

 

特殊情況

當線程處於了凍結狀態(就是等待狀態),就不會讀取到標記,那麼線程就不會結束。

這個時候怎麼結束呢?

這時要清除凍結狀態,強制讓線程恢復到運行狀態中來,這樣就可以操作標記讓線程結束。

 

Thread類中提供了該方法 interrupt() 中斷線程.

例:

    public synchronized void run() //在這加了鎖這種情況,所以下面等待就不會讓線程停下 	{	
    while(flag)
    	{
    		try
    		{
    			wait();   //在這裏讓線程等待了。所以必須先喚醒線程才能讓其停止。
    		}
    		catch (Exception e)
    		{
    			System.out.println(Thread.currentThread().getName()+"................Exception");
    		}
    		ystem.out.println(Thread.currentThread().getName()+"..........run");	
    	}
    	
    }
    		
    
    while(true)
    {
    	if(num++==60)
    	{
    		st.changeFlag(); //修改標記讓線程停下
    		t1.interrupt();   // 中斷線程
    		t2.interrupt();
    		break;
    	}				
    	System.out.println(Thread.currentThread().getName()+"..........");
    }
    System.out.println("主函數運行完。。。");

 

線程帶來的好處?

1,充分利用CPU資源

2,簡化編程模型

如果要使程序完成多項任務,這時用單線程的話,就得作出判斷以及什麼時候執行。而且不好操作,使用多線程來完成呢,就方便的多。

3,使GUI更有效率。

4,節約成本。

使用多進程,提高了程序的執行效率。

5,簡化異步事件的處理。

 

線程的生命週期:開始、運行、掛起、停止 四種不同的狀態。

 

join方法的使用

程序在返回數據的過程中,而這個數據又是在線程執行過程中給賦予的,這時如果線程沒有執行結束,或還沒有給我們所調用的信息賦值,那麼這時,返回的數據就是錯誤的信息了。

這時,我們可以採用讓調用者先等待一會,在去調用,但是等多長時間又是一個問題,所以在這裏出現了join()方法。調用了join()方法,就是讓這個線程執行完,才執行下面的代碼。

例:

//  有 10 個線程

for(int  i=0; i<10; i++)

{

threads[i].start();

}

//讓每一個線程執行完後,再往下執行

for(int  i=0; i<10;  i++)

{

threads[i].join();

}

//等待線程執行完纔會執行後面的代碼

System.out.println();

 

注意:其實在這裏調用join()方法,和調用其它任意一個方法,是一樣的,只要在這裏調用了一下,就意味着線程執行完在往下執行。

 

向線程傳遞數據的三種方法:

1,通過構造函數

2,通過方法和變量

3,通過回調函數

回調函數就是事件函數。

就是把自己給別人,讓別人通過方法來給自己傳值。

例:

    下面我們給Data 類中value 賦值

class Data{

public int value = 0;

}

class Work{

public void process(Data data,int a)

{

data.value = a;

}

}

public static void min(String [] args)

{

Data data = new Data();

Work work = new Work();

work.process(  data , 3  );  //通過回調函數給value 賦值

}

 

volatile 關鍵字

用於聲明簡單類型,如 int float boolean 等。

public volatile int n = 9;

聲明後,對它們的操作就會變成原子級別的。 每次使用它都到主存中進行讀取。多線程在操作的時候操作的是同一個數據,所以保證了數據的同步。

注意:當變量值由自身的上一個值決定時,如 n=n+1  n++ 等,volatile關鍵字將失效。

 

心得:

使用線程就得注意數據同步的問題,使用join方法非常好,在線程中還有一個問題,就是在給代碼加上鎖的時候注意別產生死鎖。

 

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