java多線程簡單案例入門

本文講解3點:

一. 對比單線程和多線程的區別

二. 創建多線程的2種方式:1. extends Thread  2.implements Runnable

三. 線程同步


 一. 對比單線程和多線程的區別

1. 單線程

TestThread.java

public class TestThread {
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadDemo().run();
		while(true){
			
			System.out.println("main():"+Thread.currentThread().getName());
		}

	}

}

class ThreadDemo{
	
		public void run(){
			while(true){
				
				System.out.println("run():"+Thread.currentThread().getName());
			}
		};
		
}

運行結果截圖:

結果分析:其實這只是一個普通的單線程程序,所以main()方法裏 while()的代碼不會被執行到。而且,new ThreadDemo().run(); 是運行在main 線程內。

2. 多線程  (TestThread.java 改成一個多線程,對比運行結果)

   TestThread2.java

public class TestThread2 {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadDemo2().start();
		while(true){
			
			System.out.println("main():"+Thread.currentThread().getName());
		}

	}

}

class ThreadDemo2 extends Thread{
	
		public void run(){
			while(true){
				
				System.out.println("run():"+Thread.currentThread().getName());
			}
		};
		
}

運行結果截圖:

  

分析結果: 線程ThreadDemo2 和 線程main 交替執行,無序。

跟上面的TestThread.java對比,2點區別:1. TestThread.java 中ThreadDemo不是線程,因爲沒有extends Thread 也沒有implements Runnable , 所以 TestThread.java 是普通的單線程程序。 而 TestThread中ThreadDemo2是線程,因爲extends Thread .  2, ThreadDemo2 的啓動線程的方法是start(),不是run().

-----------------------------------------------------------------------------------------------------------

二. 創建多線程的2種方式:1. extends Thread  2.implements Runnable


一). 用Thread類創建線程

1. 要將一段代碼在一個新的線程上運行,該代碼應該在一個類的run()函數中,並且run()函數所在的類是Thread的子類。倒過來看,我們要實現多線程,必須編寫一個繼承了Thread類的子類,子類要覆蓋Thread類中的run()函數,在子類的run()函數中調用想在想在新線程上運行的程序代碼。

2. 啓動線程,要start()方法,而不會用run()方法。

3. 由於線程的代碼段在run方法中,那麼該方法執行完以後線程也就響應結束了,因而可以通過控制run方法中循環的條件來控制線程的結束。

1)前臺線程,守護線程(也叫後臺線程)和聯合線程

  1. 如果對某個線程對象在啓動(調用start方法)之前調用了setDaemon(true)方法,這個線程就變成了守護線程。

  2. 對於java程序來說,只要還有一個前臺線程在運行,這個進程就不會結束;如果一個進程中只有守護線程運行,這個進程就會結束。‘

  3. pp.join()的作用是把pp所對應的線程合併到調用pp.join();語句的線程中。

3.1 TestThread3.java

public class TestThread3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadDemo3().start();

	}	
	

}

class ThreadDemo3 extends Thread{
	
	public void run(){
		while(true){
			
			System.out.println("run():"+Thread.currentThread().getName());
		}
	};
}

運行結構截圖:

程序分析:main()方法執行完 new ThreadDemo3().start(); 這條語句後,main方法就就結束了,也就是main方法所在的main線程就結束了。注意:雖然main線程結束了,但是java程序並沒有結束,因爲前臺線程ThreadDemo3 還在執行。此程序說明:java程序中,只要還有一個前臺線程在運行,那麼java程序都不會結束(雖然main線程已經結束了)。


3.2 TestThread4.java

public class TestThread4 {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread tt = new ThreadDemo4();
		tt.setDaemon(true);//設置線程爲 守護線程
		tt.start();

	}	
	

}

class ThreadDemo4 extends Thread{
	
	public void run(){
		while(true){
			
			System.out.println("run():"+Thread.currentThread().getName());
		}
	};
}

運行結果截圖:


程序結果分析:ThreadDemo4爲守護線程,java程序中只有守護線程時,java程序馬上結束。


4. TestThread5.java (解析 tt.join() , tt.join(10000) 的意義)

public class TestThread5 {

	public static void main(String[] args) {
		
		ThreadDemo5 tt = new ThreadDemo5();
		tt.start();
		
		int index = 0 ;
		
		while(true){
			
			if (index ++ == 100) {
				try {
					<span style="color:#cc0000;">tt.join();</span>
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			System.out.println("main():"+Thread.currentThread().getName());
		}

	}e.printStackTrace();

}

class ThreadDemo5 extends Thread{
	
		public void run(){
			while(true){
				
				System.out.println("run():"+Thread.currentThread().getName());
			}
		};
		
}

程序分析:

1. tt.join() .  運行結果是: 剛開始 ThreadDemo5 tt線程 和 main線程交替運行,當main線程的 index 達到100時 , 因爲tt.join,則表示 tt線程加入main線程,即tt線程和main線程合併成一個新的單線程。程序運行結果是: 剛開始 thread-0 , main 交替 ,  當main線程的 index 達到100時,一直是 thread-0 到最後。 因爲 當main線程的 index 達到100時,tt線程已經加入main線程,變成一個單線程,所以執行到tt線程時,要等到tt線程執行完才繼續執行main線程。

2. tt.join(10000) . 運行結果是:剛開始 tt線程和main線程交替運行,當main線程的index 達到100時,則tt線程加入main線程成爲一個新的單線程(10秒),10秒過後,tt線程和main線程再次各自成爲單獨的線程,形成剛開始的多線程。即:tt.join(10000) 表示:tt線程加入main線程形成新的單線程10秒鐘。


二) . 用 實現Runnable 接口的方式創建線程

class ThreadDemo implements Runnable { public void run(){}};

main(){  Thread tt = new Thread(new ThreadDemo()); tt.start(); }

三). 兩種方式創建 線程的區別

eg: 火車站賣票100張,分四個窗口同時賣。比較使用extends Thread 和 implements Runnable 的區別。

對比結果:推薦使用Runnable ,幾乎所有要實現多線程的程序,都推薦使用implements Runnable。

1. 使用extends Thread,java程序會默認儘量使用1個thread來賣票。

2. 使用 implements Runnable , java 程序會自動調用4個thread來賣票。 

4.1 TestThread6.java

package com.thread;

public class TestThread6 {

	
	public static void main(String[] args) {
		
		new ThreadDemo6().start();
		new ThreadDemo6().start();
		new ThreadDemo6().start();
		new ThreadDemo6().start();
	}

}

class ThreadDemo6 /*implements Runnable*/ extends Thread{
	
	int tickets = 100 ;
		public void run(){
			while(tickets>0){
			//	if(tickets>0)
				{
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);
				}
				
			}
		};
		
}

運行結果:4個線程分別各賣100張票。

4.2 TestThread7.java

package com.thread;

public class TestThread7 {

	public static void main(String[] args) {
		ThreadDemo7 t = new ThreadDemo7();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}

}

class ThreadDemo7 implements Runnable{
	
	int tickets = 100 ;
		public void run(){
			while(tickets>0){
			//	if(tickets>0)
				{
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);
				}
				
			}
		};
		
}

運行結果:


---------------------------------------------------------------------------------------------------------------------------

三. 線程同步

線程同步的方法:使用synchronized 來實現 要確保線程安全的代碼塊 的原子性操作。

先看一段線程不安全的代碼,以賣票100張爲例。本例中4個線程賣100張票,則可能會打印出0和負數。正常應該是(100~1).

出現異常的原因是:當線程1執行到 while(tickets>0)這一句時,當時tickets = 1 ,這時操作系統突然切換到線程2(線程1並沒有執行system.out.print(tickets--)),線程2此時判斷tickets=1,並打印了“賣出票1”,tickets-- 變成了 0 ,這時 操作系統又切換到線程1讓它繼續執行system.out....(tickets--) ,打印出 "賣出票0" 。 同理,若線程3之前執行到"while(tickets>0)”時當時tickets=2,還沒有執行system.out()這一句就被操作系統切換走了,而後來當又切換到線程3執行 system.out.print(tickets--) 時,tickets 可能當時已經變爲-1了,所以,線程3 打印“賣出票-1” , 同理,可以退出打印“賣出票-2” 的情況。

分析原因:

因爲 while(tickets>0)這一句 沒有 和 循環體內的 代碼塊{system.out.println(tickets--)} 保持同步操作。即應該保持while(tickets>0)和{system.out.println(tickets--)}代碼塊操作的原子性。

注意:Thread.sleep(10) 這一句只是爲了模擬出更容易出現線程不安全的狀況,不加這一句,程序也是線程不安全的。

5.1 TestThread8.java

package com.thread;

public class TestThread8 {
	
	public static void main(String[] args) {
	
			ThreadDemo8 t = new ThreadDemo8();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			
		}

	}

	class ThreadDemo8 implements Runnable{
		
		int tickets = 100 ;
			public void run(){
				
				while(tickets>0){					
						
					/*線程休眠10毫秒,模擬出線程不安全的情況出現。不加sleep()也是線程不安全的,加了sleep()則不安全的結果會更容易出現。*/
					try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);
					
					
				}
			}
			
	}

結果截圖:


爲了確保while(tickets>0)和{system.out.println(tickets--)}代碼塊操作的原子性,用synchronized(對象)來實現,看下面程序。

5.2 TestThread9.java

package com.thread;

public class TestThread9 {

	
	public static void main(String[] args) {
		ThreadDemo9 t = new ThreadDemo9();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}

}

class ThreadDemo9 implements Runnable{
	
	int tickets = 100 ;
	String str = "";
		public void run(){
			
			<span style="color:#ff0000;">synchronized (str)</span> { /* <span style="color:#ff0000;">synchronized (任意對象)可實現代碼塊的原子性*/</span>
				
				while(tickets>0){						
					//線程休眠10毫秒,模擬出線程不安全的情況出現。不加sleep()也是線程不安全的,加了sleep()則不安全的結果會更容易出現。
					try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
				}
			}
			
		}
		
}

運行結果:

注意 :用synchronized(任一對象名){代碼塊} ,可實現代碼塊原子性操作,確保代碼塊的同步。因爲每一個對象都有一個標誌位,0或者1. 

當str標誌位爲1時,線程進入代碼塊,str標誌位立刻變爲0,變爲阻塞狀態,其他線程就被阻塞。直到synchronized代碼塊執行完,str的標誌位變爲1,纔會取消阻塞狀態,java程序才能切換到其他線程執行。

str的標誌位又稱:監視器的鎖旗標。

當其他線程訪問到str監視器時,若str監視器的鎖旗標爲0,則其他線程將會進入一個因str監視器鎖旗標而產生的等待線程池中。直到str監視器的鎖旗標變爲1時,纔可能享有str監視器的鎖旗標。

注意:String str = "" ; 要放在run()方法外。

還可以使用同步函數達到同步效果,看代碼:

5.3 TestThread10.java

package com.thread;

public class TestThread10 {

	
	public static void main(String[] args) {
		ThreadDemo10 t = new ThreadDemo10();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}

}

class ThreadDemo10 implements Runnable{
	
	int tickets = 100 ;
	String str = "";
	public void run(){
		sale();
		
	}
	public <span style="color:#ff0000;">synchronized</span> void sale(){
		
		while(tickets>0){						
			//線程休眠10毫秒,模擬出線程不安全的情況出現。不加sleep()也是線程不安全的,加了sleep()則不安全的結果會更容易出現。
			try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
			System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
		}
		
	}
		
}

注意: 在 方法名前加上 synchronized 關鍵字,則 sale()方法是線程安全的,監視器是 this 對象。

即可使用同步代碼塊來實現線程之間的同步,也可以使用同步函數來實現線程之間的同步。

怎樣使得同步代碼塊和同步函數保持同步? 使用同一個監視器this即可。看下面的程序。

5.4 TestThread11.java

package com.thread;

public class TestThread11 {
	
	public static void main(String[] args) {
		
		ThreadDemo11 t = new ThreadDemo11();				
		new Thread(t).start();
		try { Thread.sleep(1);} catch (Exception e) { e.printStackTrace();}
		t.str = "method";		
		new Thread(t).start();
		
	}

}

class ThreadDemo11 implements Runnable{
	
	int tickets = 100 ;
	String str = "";
	public void run(){
		
		if (str.equals("method")) {
			sale();
		}else {
			
			synchronized (<span style="color:#ff0000;">this</span>) {
				
				while(tickets>0){						
					//線程休眠10毫秒,模擬出線程不安全的情況出現。不加sleep()也是線程不安全的,加了sleep()則不安全的結果會更容易出現。
					try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
				}
			}
			
		}
		
		
	}
	public synchronized void sale(){
		
		while(tickets>0){						
			//線程休眠10毫秒,模擬出線程不安全的情況出現。不加sleep()也是線程不安全的,加了sleep()則不安全的結果會更容易出現。
	//		try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
			System.out.print("sale-");
			System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
		}
		
	}
		
}

注意:讓同步代碼塊 和 同步函數同步 的方法:讓他們共用一個監視器。本例中共同的監視器是this對象,不能是str。因爲同步函數的監視器是this對象,所以必須是的同步代碼塊的監視器也爲this對象。synchronized (this) 是正確的,若寫成 synchronized(str) ,則同步代碼塊和同步函數不能同步。






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