Java 多線程理解(一) 多線程技能

1.概念

關於多線程的基礎概念不做過多概述!

線程:簡單來講,就是程序運行的最小單位,比如我們運行的main方法也是一個線程。一個進程可能有幾個線程在運行計算數據。

多線程的優點:多任務操作系統的有點相信都體會過了,可以最大程度上利用CPU 的空閒時間,CPU在多個線程來回切換,大大減少單任務處理由於進程等待帶來的空閒時間的CPU空閒,線程之間是相互獨立的。

2.使用多線程

 想要學習一個技術就得接近它 控制它

1) 實現方式

a.繼承Thread

b.實現Runnable接口

先來看看Thread的結構

public class Thread implements Runnable {

從源代碼可以發現Thread實現了Runable接口,他們之間有多態關係

其實,使用繼承Thread方式最大的侷限就是多繼承,所以爲了支持多繼承,完全可以實現Runnable接口,一邊繼承一邊實現。兩種方式沒有本質區別,一個是重載一個重寫。

public class Manager extends Thread{
	
	private List<String> queue = new LinkedList<String>();
	@Override
	public void run(){
		System.out.println();
	}
	
	public static void main(String[] args) {
		Manager m = new Manager();
		
		m.start();
	}
}

線程執行特性:

1.隨機性

2.不確定性

多線程執行的先後順序是不確定性的,調用start()方法只是告訴"線程規劃器" 我已經做好執行的準備了  具體執行順序讓系統安排一個時間去執行run() 方法。start()的先後順序不決定具體的線程執行順序。具體代碼這裏就不貼了。

2) 線程安全

自定義線程類中的實例變量有共享變量和飛共享變量之分。

a.不共享的情況

各自線程有各自的實例變量相互不影響,多線程操作也能保證安全

b.共享情況

public class Manager extends Thread{
	int count = 10;
	@Override
	public void run(){
		super.run();
		count--;
		System.out.println("由"+Thread.currentThread()+"線程計算count:"+count);
	}
	public static void main(String[] args) {
		Manager m = new Manager();
		
		Thread p = new Thread(m,"Manager1");
		Thread c = new Thread(m,"Manager2");
		Thread v = new Thread(m,"Manager3");
		p.start();
		c.start();v.start();
	}
}

可以發現執行結果:
由Thread[Manager2,5,main]線程計算count:8
由Thread[Manager3,5,main]線程計算count:7
由Thread[Manager1,5,main]線程計算count:8

線程1、3對count同時進行了操作,產生非線程安全。想要的結果是依次遞減打印。
i- -可以分解3步:

1.去的原有i的值;

2.計算i - -;

3.給i進行賦值;

如果多線程對着個參數進行操作,很有可能在第一步兩個線程同時取到同樣的數據,導致結果重複;其實很典型的列子就是消費者模式,多個消費者做在同一張桌子上吃飯,生產者端吃的。消費者必須等生產者端上來才能開始吃,也就是安排隊的方式。具體怎麼處理後續說明

3) sleep方法

簡單介紹一下sleep()方法於我的理解,當然還有currentThread、isAlive、getId等方法,就不一一介紹了。

a)currentThread() 方法是獲取當前線程對象

b)sleep() 方法是讓當前線程在指定的毫秒數暫停。是指的this.currentThread()返回的線程

這裏就要說到sleep和wait()的區別了:

1.sleep() 是Thread類的一個靜態方法 讓調用的當前線程暫停一段時間  wait 是Object 的方法

2.sleep 不會釋放對象鎖、放棄CPU ,wait會釋放對象鎖 且讓出CPU

3.wait 必須依賴於synchroid ,否者會拋出java.lang.IllegalMonitorStateException 異常,先要獲取對象鎖 可以通過notify 、notifyall 喚醒

4) 停止線程

停止線程意味着在線程處理完任務之前停掉正在做的任務,看似簡單,但必須做好防範,以便達到預期的效果。

停止線程可以用Thread.stop()  ,強制性停止當前對象線程,而且會釋放鎖,導致和預期不一致的結果

1) interrrupt()

並不能真正的停止線程:

public class ThreadTest extends Thread{
	@Override
	public void run(){
		for(int i = 0; i< 50000;i++){
			System.out.println("i:"+i);
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.start();
		
		test.interrupt();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("interrupt");
		test.stop();
	}
}

運行結果:
i:49994
i:49995
i:49996
i:49997
i:49998
i:49999
可以看出,調用interrupt() 方法後線程並沒有立即停止,而是設置線程停止標誌.

2)判斷線程是否停止

a)Thread. interrupted()  測試當前線程是否停止 靜態方法

b)this.isInterrupted() 判斷線程是否停止 實例方法

那麼這兩個方法有什麼區別呢?先來看看interrupted的解釋:測試當前線程是否中斷

public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.start();
		
		Thread.currentThread().interrupt();
		System.out.println(Thread.interrupted());
		System.out.println(Thread.interrupted());
		System.out.println("interrupt");
	}
運行結果:
true
false
interrupt
i:0
從上述結果來看 ,interrupted()的確判斷出了線程是否中斷狀態,但爲什麼第二個位false呢,從官方幫助文檔查閱發現:

測試當前線程是否中斷,線程的中斷狀態由改方法清除。換句話說調用這個方法後線程的中斷狀態會被清除,這就是爲什麼第二次調用會是false。

再來看isInterrupted()方法:終端正在運行的線程,不是static方法;

public class ThreadTest extends Thread{
	@Override
	public void run(){
		for(int i = 0; i< 50000;i++){
			System.out.println("i:"+i);
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Thread.currentThread().interrupt();
		test.interrupt();
		System.out.println(test.isInterrupted());
		System.out.println(test.isInterrupted());
		System.out.println("interrupt");
		
		test.stop();
		
	}
}
運行結果:
i:306
true
true
interrupt
可以看到並沒有清除狀態。

最後再來看下這兩個方法的解釋吧:
a) interrupted() 測試當前線程中斷狀態,並具有清除標誌置爲false的功能,靜態方法

b) isinterrupted() 測試線程的終端狀態,但不清狀態標誌  實例方法

3) 能停止的線程

據以上的內容,可以用在線程中判斷線程狀態的方式來停止線程:

public class ThreadTest extends Thread{
	@Override
	public void run(){
		for(int i = 0; i< 50000;i++){
			if(this.isInterrupted()){
				break;
			}
			System.out.println("i:"+i);
		}
		System.out.println("thread is end");
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Thread.currentThread().interrupt();
		test.interrupt();
		System.out.println(test.isInterrupted());
		System.out.println(test.isInterrupted());
		
	}
}
運行結果:
i:278
i:279
true
true
thread is end

從運行結果上看:線程跳出了循環,但是線程並沒有真正的停止。如何解決呢,看下更新後的代碼:

public class ThreadTest extends Thread{
	@Override
	public void run(){
		try {
			for(int i = 0; i< 50000;i++){
				if(this.isInterrupted()){
					throw new InterruptedException();
				}
				System.out.println("i:"+i);
			}
			System.out.println("thread is end");
		} catch (Exception e) {
			System.out.println("線程停止,進入catch塊");
			// TODO: handle exception
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Thread.currentThread().interrupt();
		test.interrupt();
		System.out.println(test.isInterrupted());
		System.out.println(test.isInterrupted());
		
	}
}
運行結果:
i:292
true
true
線程停止,進入catch塊
由此可見線程進入異常停止!

4) 沉睡中停止

如果線程在sleep中停止呢?會發生什麼情況:

public class ThreadTest extends Thread{
	@Override
	public void run(){
		try {
			Thread.sleep(200000);
			System.out.println("thread is end");
		} catch (Exception e) {
			System.out.println("線程停止,進入catch塊");
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		test.interrupt();
		System.out.println(test.isInterrupted());
		System.out.println(test.isInterrupted());
		
	}
}
運行結果:

false
false
線程停止,進入catch塊
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.wyj.design.consumer.ThreadTest.run(ThreadTest.java:7)
從以上結果來看,在沉睡中停止線程,會進入到catch(),且清除了線程運行狀態標誌。sleep不會釋放線程鎖及CPU,導致中斷拋出異常

相反如果打斷後在沉睡呢:

i:199998
i:199999
線程停止,進入catch塊
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.wyj.design.consumer.ThreadTest.run(ThreadTest.java:11)
從以上結果來看,先中斷線程,在沉睡會進入到catch()。

5) 能停止的線程

暴力的停止線程stop().

調用stop() 會拋出ThreadDeth 異常,通常情況下不需要現行的捕捉。

stop 方法已被JDK標記爲過期的方法作廢

1.stop方法會釋放鎖 可能會導致線程安全

2.異常停止線程導致結束流程沒有執行

5) 守護線程

在java線程中有兩種線程:用戶線程、守護線程

守護線程是一種特殊的線程,隨着用戶線程,在JVM中只要有一個非守護線程,守護線程就在工作,最常見的是垃圾回收線程。JVM所有線程結束了,垃圾回收線程也就結束了。

public class ThreadTest extends Thread{
	@Override
	public void run(){
		try {
			int i = 0;
			while(true){
				
				System.out.println("i:"+i++);
				Thread.sleep(1000);
			}
		} catch (Exception e) {
			System.out.println("線程停止,進入catch塊");
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest();
		test.setDaemon(true);
		test.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
運行結果:

i:0
i:1

用戶線程結束了,守護線程也就結束了。

3 小結

本文介紹了Thread的一些基礎知識。在使用線程的時候可能會發生很多意想不到的情況,這也是多線程一些不可預知的的一個體現,需要學習這個基礎知識和一些常用的情況來規避這些問題。

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