多線程通信


1.包(package):包名具有唯一性。

在windows中的具體體現:文件夾。
1.包的作用:1.分類管理類文件;
2.對類提供了多層名稱空間。
2.如何定義:使用關鍵字 package,定義必須定義在源代碼的第一行,類名的全稱是:包名.類名;
包名全小寫如mypackage.
3.步驟:1.javac -d 包的路徑 全類名.java
2.set classpath=包的路徑;

3.java 包名.類名

一個包中不能存在兩個public修飾的類。
被訪問的包中,類和方法都需要被public修飾,否則權限不夠。

4.包與包之間的繼承
protected:包的修飾符,保護權限,只有子類才能用;用於保護父類中的成員。
protected 保護子類使用:必須子類纔可以用,非子類則不可以用。

四種權限:  public protected   default(默認的)private
同一個類中:  ok    ok      ok   ok
同一個包中:  ok            ok            ok 
子類:      ok            ok
不同包中:      ok
    
5.簡化書寫:使用import關鍵字導入,格式:import 全類名;導入的都是包中的類;若是需要導入多個類,則使用通配符,如 import packa.*。
而在真實開發時,用到哪個則導入哪個,根本也用不到導入,用高級編輯器。
6.注意:
1.import packa.* :只導入packa包的當前目錄下的所有類,而不導入當前目錄下的包。
2.當導入的包中的類有重名時,使用該類時,必須用包名.類名的全名稱,否則無法區分,會產生使用的不確定性。
  如 import packa.Demo;
     import packb.Demo;

不能new Demo(),而需new packa.Demo();

2.Jar包:java的壓縮包。
-c:創建新的歸檔文件
-x:解壓
java壓縮工具:jar cvf haha.jar pack
jar包的基本使用。只要將類和包都存儲到jar中,方便於使用。只要將jar配置到classpath路徑下。 
  壓縮:jar cf 歸檔名.jar 包名
  解壓:jar -xvf 歸檔名.jar

3.多線程技術
1.進程:正在進行中的程序。
2.線程(Thread):負責進程進行執行。一個進程至少有一個線程,若有多個則稱爲多線程程序;線程也可以稱爲執行線程、執行路徑、執行背景。
3.好處:可以同時執行多個程序。
4.JVM多線程的基本體現:
JVM也是多線程,至少有一個主線程和垃圾回收線程。主線程執行的是主函數中的內容,垃圾回收線程執行的對堆內存進行內存釋放的代碼。
5.垃圾回收的方法:finalize(),垃圾回收
 gc():運行垃圾回收器,System.gc();啓動後不一定立即執行,因爲CPU在隨機做着切換,先切換到哪就先執行哪個。
6.自定義線程:
    通過API Thread類的查閱,兩種方式:
1.繼承Thread類,覆寫run()方法,創建Thread子類對象,調用start方法來開啓線程並調用run方法;線程類在描述線程時,有一個功能函數run,專門  用於封裝執行的任務。
 主線程也是同理,該線程執行的任務在主函數中。
     
2.定義一個實現Runnable接口的類,然後該類實現 run 方法。然後可以分配該類的實例,在創建線程時作爲一個參數來傳遞並啓動。
1.定義類實現Runnable接口;
  2.覆寫該接口中的run()方法;
  3.通過Thread類創建線程對象;
  4.將實現Runnable接口的實現類的對象作爲參數傳遞給Thread類的構造函數,將run方法所屬的對象傳遞給線程,讓線程去執行我們指定的代碼任務;
  5.調用start方法,目的是開啓線程並調用run方法。
     
  Thread t=new Thread();創建進程。
1.覆寫run方法的原因:創建進程的目的是爲了運行指定的代碼,任務就應該自己指定。

2.特定方法start():開啓線程並調用run方法運行線程。
start和run區別:調用run方法,並沒有開啓新的線程,都是由主線程來執行,無法完成同時運行的需求;
       調用start方法,開啓了新的線程並調用了線程的任務方法run方法,實現了代碼同時執行。

3.執行路徑是在每一條路徑上先進後出的,即每個線程都有自己單獨的一片棧內存空間。
4.currentThread():返回當前正在執行的進程,Thread.currentThread().getName();結果是Thread-編號,編號從0開始。

5.線程是分區來進行管理的,當前線程發生異常隻影響當前線程,不影響其它線程。
6.主線程是最後結束的線程,只要還有存活的線程,它就不能結束。
7.創建線程的目的:爲了開闢一個執行路徑去執行指定的代碼,讓其和其它代碼同時運行;意味着每一個線程的創建都有自己要運行的內容,而這個內容也稱爲線程的任務。

7.線程的隨機性的導致的原因?
一個CPU在處理多個線程時,進行着隨機的快速切換,切換到哪個線程就執行哪個線程,從而出現了隨機性。

8.什麼時候使用多線程?以及創建線程的目的?
     目的:爲了開闢一個執行路徑去執行指定的代碼,讓其和其它代碼同時運行;意味着每一個線程的創建都有自己要運行的內容,而這個內容也稱爲線程的任務。

     使用:爲了讓多個執行路徑同時進行,各自運行各自的內容,就可以使用多進程。

4.線程的四種狀態:被創建、運行、凍結notify、sleep、wait、消亡(還有一個特殊狀態:就緒)

每一個狀態的特點?
  被創建:調用start()方法,開啓線程並調用run()方法;
  運行:該狀態是持有執行資格和CPU的執行權;
  凍結:既不具備CPU的執行權,也不具備執行資格;運行狀態時調用了sleep()方法,當時間到了才轉回運行狀態;
  消亡:運行狀態的run方法執行完成或者調用stop()方法;釋放內存。

5.瞭解



6·Runnable接口出現的好處
  1.避免了單繼承的侷限性(線程對象和線程任務的耦合性很強);
  2.按照面向對象的思想,將線程運行的任務進行單獨的對象封裝,降低了線程對象和線程任務的耦合性。
 
  即:繼承Thread類時,子類對象是一個線程對象;而在實現Runnable接口時,子類對象是一個線程任務,創建新的線程對象,
  將要執行的線程任務作爲參數傳遞給線程對象,通過Thread類的構造函數將線程對象和線程任務關聯起來。
		class Demo implements Runnable
		{
			//將線程要運行的任務代碼封裝到了該run方法中。 
			public void run()
			{
				for(int x=1; x<=20; x++)
				{
					System.out.println(Thread.currentThread().getName()+"......"+x);
				}
			}
		}
		class ThreadDemo4
		{
			public static void main(String[] args) 
			{
				Demo d = new Demo();


				//創建線程對象。因爲沒有了Thread的子類。所以通過Thread類來完成線程對象的創建。
				Thread t1 = new Thread(d);
				Thread t2 = new Thread(d);
				t1.start();//開啓了線程並調用了線程中的run方法。只不過線程類中的run方法並沒有定義我們要讓線程運行的內容。 
				t2.start();

7·四個窗口售票的案例
	 class Ticket implements Runnable
	 {
	 	private int num = 100;
	 //	爲了給同步代碼塊指定一個對象。可以用自定義的,也可以用已有的。 比如Object
	 	private Object obj = new Object();
	 	public void run()
	 	{
	 		while(true)
	 		{
	 			synchronized(obj)//這個對象就相當於一個鎖。
	 			{
	 				if(num>0)
	 				{
	 					try{Thread.sleep(10);}catch(InterruptedException e){}//讓當前線程在這裏小睡一下。
	 					System.out.println(Thread.currentThread().getName()+"......"+num--);
	 				}
	 			}
	 		}
	 	}
	 }

	 class TicketDemo2 
	 {
	 	public static void main(String[] args) 
	 	{
	 		Ticket t = new Ticket();
	 		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();
	 	}
	 }
內存圖分析:


8·線程的安全問題

產生原因分析:1.多個線程在處理同一共享數據;
  2.線程任務代碼中有多條語句在操作這個共享數據,這些多條操作共享數據的語句被線程分開進行執行,在一個線程執行這些語句的過程中,其它線程參與了運算導致數據的錯誤。

  線程任務中只要存在着:共享數據、多條操作共享數據的語句,就會產生安全問題。

9·安全問題的解決
  解決思想:對多條操作共享數據的語句,只能讓一個線程都執行完,執行過程中,其它線程不可參與執行;
  將多條操作共享數據的語句進行封裝,只有一個線程在內執行,運行期間其它進程進不來,只有這個線程執行完,其它線程纔有執行的機會。
  方式:通過synchronized(對象){需要同步的代碼;}需要同步的代碼包括所有操作共享數據的代碼,對象可以自定義,也可以是已有的,對象出來後再釋放給
  同步代碼塊。

10·同步的好處:解決了多線程的安全問題;
  弊端:降低了效率,同一時刻只能有一個線程運行;而且易出現死鎖。
  前提:必須是多個線程,使用的是同一個鎖對象。

11·同步函數:在函數上加入同步關鍵字synchronized;
  同步函數和同步代碼塊的區別:
  1.同步函數在寫法上較簡潔;
  2.同步函數的鎖對象是固定的,而同步代碼塊的鎖對象是任意的,建議用同步代碼塊,尤其是在代碼中使用多個同步不同鎖時,必使用同步代碼塊。

同步函數,和靜態同步函數使用的鎖是:
   同步函數:對象.getClass();
   靜態:類名.class

12·懶漢式:當多線程併發訪問時,會出現多線程的安全問題,無法保證對象的唯一性,這時用同步函數來解決這個問題,但同時產生新的問題:降低性能。
  解決性能降低的方法:通過雙重判斷的格式,寫同步代碼塊且在代碼塊前加if判斷語句,好處是:減少判斷鎖的動作,相對提高了性能。
 	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 Test
	{
		public static void main(String[] args) 
		{
			System.out.println("Hello World!");
		}
	}

13·死鎖:
  常見情況之一:同步的嵌套,即同步中還有同步,而鎖對象不同。
  一個運行在flag爲true的同步代碼塊中,另一個運行在flag爲false的同步函數中,讓同步函數中有同步代碼塊,同步代碼塊中有同步函數。
	class Test implements Runnable
	{
		private static final Object lock1=new Object();
		private static final Object lock2=new Object();
		private boolean flag;
		Test(boolean flag)
		{
			this.flag=flag;
		}
		public void run()
		{
			if (flag)
			{
				while (true)
				{
					synchronized(lock1)
					{
						System.out.println("if ....lock1 ");
						synchronized (lock2)
						{
							System.out.println("if ....lock2");
						}
					}
				}
			}
			else 
			{
				while (true)
				{
					synchronized(lock2)
					{
						System.out.println("else ....lock2 ");
						synchronized (lock1)
						{
							System.out.println("else ....lock1");
						}
					}
				}
			}
		}
	}
	class DeadLockTest 
	{
		public static void main(String[] args) 
		{
			Test t1=new Test(true);
			Test t2=new Test(false);


			Thread ta=new Thread(t1);
			Thread tb=new Thread(t2);
			ta.start();
			tb.start();
		}
	}

14·線程間通信:有多個線程在處理同一個資源,但是處理的動作卻不一樣。(一進一出)
  示例:資源中有姓名和性別,一個線程負責賦值,一個線程負責取值。
  //創建資源對象;
  //創建線程任務對象;
  //創建線程對象;
  //開啓線程。
  分析:input:在資源中定義標記用於判斷資源是否有值,boolean flag=false;當標記爲true,說明有值,input應該等待,讓output運行。
   output:當資源中標記爲false,說明沒值,output執行輸出,輸出完改標誌flag=true,


16·多線程間通信最爲重要的機制:等待喚醒機制。涉及方法:wait(),notify(),notifyAll().
 
  wait():可以讓線程處於凍結狀態,釋放執行資格和執行權。同時將線程存儲到一個線程池中,wait前先釋放鎖。
  notify():只喚醒線程池中的一個線程,隨機任意的。

  nitifyAll():喚醒線程池中的所有線程,讓所有線程都具備執行資格。

  

  這些方法就是用在鎖上的,都必須使用在同步中,因爲同步中帶有鎖,同步可以有多個,但只有一個同步鎖,用同步鎖來區分。
  所以在使用時,必須要明確wait的是哪個鎖上的線程,notify的是哪個鎖上的線程,調用時用鎖對象來調用。
 
  爲什麼這些方法定義在Object中?這些方法都用在同步中,而同步鎖是任意對象,任意對象都能用的方法應該定義在Object類中。

17·生產者消費者案例
    多生產者和多消費者:
問題:發生數據的錯亂。
原因:被喚醒的線程沒有去判斷標記,直接進行了執行。
解決:用while循環,當它被notify後,再去判斷一下標記。
新問題:本方在喚醒過程中可能喚醒到本方線程,本來只需喚醒對方一個線程就可以了,導致效率過低。
解決:用新版本,將鎖對象與監視器分離,一個鎖對象綁定兩組監視器,一組監視生產者,一組監視消費者。


18·多線程技術的升級:JDK1.5版本中提供了一個java.util.concurrent.locks.Lock接口,對鎖進行單獨的描述個封裝,並定義操作的鎖的方法在鎖對象中。
  同步代碼塊和同步函數:對鎖的操作是隱式的;
  Lock接口:顯示操作鎖。

  用Lock接口代替同步後,不能再有wait().notify().notifyAll()等方法。
  而提供了Condition接口,將操作鎖上線程的狀態方法(等待,喚醒,喚醒所有)進行了單獨的封裝,都封裝到了Condition對象中。
  Condition改寫:以前要解決,希望有兩組監視器放分別監視不同的線程,一組監視生產者,一組監視消費。
  但是之前監視器方法和鎖是綁定的,兩組監視器需要兩個鎖,很有可能出現同步嵌套,很容易出現死鎖.

  但改寫後問題依舊存在,本方還是一樣會喚醒全部(本方).
  解決方案:一個鎖對象,綁定多組監視器。一組監視生產者,一組監視消費者。

  好處:以前是一個監視器方法上只能有一個鎖,現在一個鎖能有多個監視器方法。      
 	     import java.util.concurrent.locks.*;
	     class Res
	     {
	       	private String name;
	       	private int count = 1;
	       	private boolean flag;
	       	//創建一個顯示鎖對象。
	       	private Lock lock = new ReentrantLock();
	       	//獲取到該lock鎖上監視器對象。該監視器對象負責監視生產者。
	       	private Condition producer_con = lock.newCondition();
	       	private Condition consumer_con = lock.newCondition();
	       	
	       	public  void set(String name)// 
	       	{
	       		// 獲取鎖。顯示方法 
	       		lock.lock();
	       		try
	       		{
	       			while(flag)
	       				try{producer_con.await();}catch(InterruptedException e ){}//t0(t2將其喚醒  wait) t1(被t0喚醒了 wait)
	       			
	       			this.name = name+count;//商品-1  商品-2  商品-3
	       			
	       			count++;//2 3 4


	       			System.out.println(Thread.currentThread().getName()+"...生產者...."+this.name);//生產 商品-1  生產 商品-2 生產 商品-3


	       			flag = true;
	       			consumer_con.signal();
	       		}finally{
	       			//釋放鎖。
	       			lock.unlock();
	       		}
	       	}
	       	public  void out()// 
	       	{
	       		lock.lock();
	       		try
	       		{
	       			while(!flag)
	       				try{consumer_con.await();}catch(InterruptedException e ){}//t2(wait) t3{wait)


	       			System.out.println(Thread.currentThread().getName()+"........消費者......."+this.name);//消費 商品-1
	       			flag = false;
	       			producer_con.signal();
	       		}
	       		finally
	       		{
	       			lock.unlock();
	       		}
	       	}
	       }


	     class Producer implements Runnable
	     {
	       	private Res r;
	       	Producer(Res r)
	       	{
	       		this.r = r;
	       	}
	       	public void run()
	       	{
	       		while(true)
	       		{
	       			r.set("商品-");
	       		}
	       	}
	       }


	     class Consumer implements Runnable
	     {
	       	private Res r;
	       	Consumer(Res r)
	       	{
	       		this.r = r;
	       	}
	       	public void run()
	       	{
	       		while(true)
	       		{
	       			r.out();
	       		}
	       	}


	     }
	     class ProConDemo2 
	     {
	       	public static void main(String[] args) 
	       	{
	       		Res r = new Res();
	       		Producer pro = new Producer(r);
	       		Consumer con = new Consumer(r);
	       		Thread t0 = new Thread(pro);
	       		Thread t1 = new Thread(pro);
	       		Thread t2 = new Thread(con);
	       		Thread t3 = new Thread(con);

	       		t0.start();
	       		t1.start();
	       		t2.start();
	       		t3.start();
	       	}
	     }


19·sleep和wait的區別:

  1.sleep:必須指定睡眠時間;
   wait:可以指定可以不指定,如果沒指定,必須用notify喚醒。
  2.在同步中:
  sleep:釋放執行權,但不釋放鎖對象,因爲會自動醒;
  wait:釋放執行權,還會釋放鎖。
  3.wait:必須用在同步中;
   sleep:可以不定義在同步中。

20. 停止線程  
方式:只要run方法結束,線程就停止,而run方法中通常有循環結構,所以只要控制住循環條件即可;
控制方式:定義一個boolean類型的變量flag來作爲標記,定義一個功能來改變flag值。
特殊情況:當在while循環中加入wait方法,由於開啓的線程都可能被wait,這樣線程就不能結束。
解決方法:用interupt方法,可以將被wait的線程從凍結狀態強制恢復到運行狀態;
應用場景:用於sleep的時間還沒到就需要結束線程,或者wait沒有被notify,即強制喚醒,但因其強制,所以存在異常;
改進:將改標記的操作加入到catch塊中,當interupt發生異常後,處理異常時,改標記flag=false;再回去讀標記時,結束循環。

21.setDaemon方法:參數boolean型,將線程設爲守護線程或者用戶線程;
特點:具有依耐性,只要前臺線程結束,後臺線程自動結束。

toString():返回線程名稱、線程優先級和線程組。
優先級:1-10;最小優先級1,默認優先級5,最大優先級10.如果都在搶CPU,則優先級一樣;
線程組:可以統一操作一組線程。

setPriority:設置線程優先級,如setPriority(Thread.MAX_PRIORITY)。
join():臨時加入一個線程進行執行,當主線程持有執行權執行到t1的join方法時,說明t1要加入運行,這是主線程會釋放出執行權和執行資格,t1就開始執行,主線程不依賴時間和喚醒,當t1線程執行結束,主線程自動獲得執行資格;但如果t1中有wait方法,則線程就不能結束。

yield():釋放當前線程的執行權,讓其他線程有機會具備執行權;


發佈了32 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章