Java多線程學習(一)Java多線程入門

最近聽不少面試的小夥伴說,網上每每是一篇一篇的Java多線程的文章,除了書籍沒有什麼學習多線程的一系列文章。可是僅僅憑藉一兩篇文章很難對多線程有系統的學習,並且面試的時候多線程這方面的知識每每也是考察的重點,因此考慮之下決定寫一系列關於Java多線程的文章。文章參考了高老師的《Java多線程編程核心技術》。力爭使用最短的篇幅把Java多線程的知識做以系統的講述。java

本節思惟導圖:面試

思惟導圖源文件+思惟導圖軟件關注微信公衆號:「Java面試通關手冊」回覆關鍵字:「Java多線程」免費領取。

一 進程和多線程簡介

1.1 相關概念

何爲線程?數據庫

線程與進程類似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程當中能夠產生多個線程。與進程不一樣的是同類的多個線程共享同一塊內存空間和一組系統資源,因此係統在產生一個線程,或是在各個線程之間做切換工做時,負擔要比進程小得多,也正由於如此,線程也被稱爲輕量級進程編程

何爲進程?安全

進程是程序的一次執行過程,是系統運行程序的基本單位,所以進程是動態的。系統運行一個程序便是一個進程從建立,運行到消亡的過程。簡單來講,一個進程就是一個執行中的程序,它在計算機中一個指令接着一個指令地執行着,同時,每一個進程還佔有某些系統資源如CPU時間,內存空間,文件,文件,輸入輸出設備的使用權等等。換句話說,當程序在執行時,將會被操做系統載入內存中。微信

線程和進程有何不一樣?網絡

線程是進程劃分紅的更小的運行單位。線程和進程最大的不一樣在於基本上各進程是獨立的,而各線程則不必定,由於同一進程中的線程極有可能會相互影響。從另外一角度來講,進程屬於操做系統的範疇,主要是同一段時間內,能夠同時執行一個以上的程序,而線程則是在同一程序內幾乎同時執行一個以上的程序段。多線程

1.2 多線程

何爲多線程?jvm

多線程就是幾乎同時執行多個線程(一個處理器在某一個時間點上永遠都只能是一個線程!即便這個處理器是多核的,除非有多個處理器才能實現多個線程同時運行。)。幾乎同時是由於實際上多線程程序中的多個線程其實是一個線程執行一會而後其餘的線程再執行,並非不少書籍所謂的同時執行。ide

爲何多線程是必要的?

  1. 使用線程能夠把佔據長時間的程序中的任務放到後臺去處理
  2. 用戶界面能夠更加吸引人,這樣好比用戶點擊了一個按鈕去觸發某些事件的處理,能夠彈出一個進度條來顯示處理的進度
  3. 程序的運行速度可能加快

二 使用多線程

2.1繼承Thread類

MyThread.java

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		System.out.println("MyThread");
	}
}
複製代碼

Run.java

public class Run {

	public static void main(String[] args) {
		MyThread mythread = new MyThread();
		mythread.start();
		System.out.println("運行結束");
	}

}

複製代碼

運行結果:

結果
從上面的運行結果能夠看出: 線程是一個子任務,CPU以不肯定的方式,或者說是以隨機的時間來調用線程中的run方法

2.2實現Runnable接口

推薦實現Runnable接口方式開發多線程,由於Java單繼承可是能夠實現多個接口

MyRunnable.java

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("MyRunnable");
	}
}
複製代碼

Run.java

public class Run {

	public static void main(String[] args) {
		Runnable runnable=new MyRunnable();
		Thread thread=new Thread(runnable);
		thread.start();
		System.out.println("運行結束!");
	}

}
複製代碼

運行結果:

運行結果

三 實例變量和線程安全

定義線程類中的實例變量針對其餘線程能夠有共享和不共享之分

3.1 不共享數據的狀況

MyThread.java

public class MyThread extends Thread {

	private int count = 5;

	public MyThread(String name) {
		super();
		this.setName(name);
	}

	@Override
	public void run() {
		super.run();
		while (count > 0) {
			count--;
			System.out.println("由 " + MyThread.currentThread().getName()
					+ " 計算,count=" + count);
		}
	}
}
複製代碼

Run.java

public class Run {
	public static void main(String[] args) {
		MyThread a = new MyThread("A");
		MyThread b = new MyThread("B");
		MyThread c = new MyThread("C");
		a.start();
		b.start();
		c.start();
	}
}

複製代碼

運行結果:

運行結果
能夠看出每一個線程都有一個屬於本身的實例變量count,它們之間互不影響。咱們再來看看另外一種狀況。

3.2 共享數據的狀況

MyThread.java

public class MyThread extends Thread {

	private int count = 5;

	@Override
	 public void run() {
		super.run();
		count--;
		System.out.println("由 " + MyThread.currentThread().getName() + " 計算,count=" + count);
	}
}
複製代碼

Run.java

public class Run {
	public static void main(String[] args) {
		
		MyThread mythread=new MyThread();
        //下列線程都是經過mythread對象建立的
		Thread a=new Thread(mythread,"A");
		Thread b=new Thread(mythread,"B");
		Thread c=new Thread(mythread,"C");
		Thread d=new Thread(mythread,"D");
		Thread e=new Thread(mythread,"E");
		a.start();
		b.start();
		c.start();
		d.start();
		e.start();
	}
}
複製代碼

運行結果:

運行結果
能夠看出這裏已經出現了錯誤,咱們想要的是依次遞減的結果。 爲何呢??

由於在大多數jvm中,count--的操做分爲以下下三步:

  1. 取得原有count值
  2. 計算i -1
  3. 對i進行賦值

因此多個線程同時訪問時出現問題就是難以免的了。

那麼有沒有什麼解決辦法呢?

答案是:固然有,並且很簡單。

在run方法前加上synchronized關鍵字便可獲得正確答案

加上關鍵字後的運行結果:

加上關鍵字後的運行結果

四 一些經常使用方法

4.1 currentThread()

返回對當前正在執行的線程對象的引用。

4.2 getId()

返回此線程的標識符

4.3 getName()

返回此線程的名稱

4.4 getPriority()

返回此線程的優先級

4.5 isAlive()

測試這個線程是否還處於活動狀態。

什麼是活動狀態呢?

活動狀態就是線程已經啓動且還沒有終止。線程處於正在運行或準備運行的狀態。

4.6 sleep(long millis)

使當前正在執行的線程以指定的毫秒數「休眠」(暫時中止執行),具體取決於系統定時器和調度程序的精度和準確性。

4.7 interrupt()

中斷這個線程。

4.8 interrupted() 和isInterrupted()

interrupted():測試當前線程是否已是中斷狀態,執行後具備將狀態標誌清除爲false的功能

isInterrupted(): 測試線程Thread對相關是否已是中斷狀態,但部清楚狀態標誌

4.9 setName(String name)

將此線程的名稱更改成等於參數 name 。

4.10 isDaemon()

測試這個線程是不是守護線程。

4.11 setDaemon(boolean on)

將此線程標記爲 daemon線程或用戶線程。

4.12 join()

在不少狀況下,主線程生成並起動了子線程,若是子線程裏要進行大量的耗時的運算,主線程每每將於子線程以前結束,可是若是主線程處理完其餘的事務後,須要用到子線程的處理結果,也就是主線程須要等待子線程執行完成以後再結束,這個時候就要用到join()方法了

join()的做用是:「等待該線程終止」,這裏須要理解的就是該線程是指的主線程等待子線程的終止。也就是在子線程調用了join()方法後面的代碼,只有等到子線程結束了才能執行

4.13 yield()

yield()方法的做用是放棄當前的CPU資源,將它讓給其餘的任務去佔用CPU時間。注意:放棄的時間不肯定,可能一會就會從新得到CPU時間片。

4.14 setPriority(int newPriority)

更改此線程的優先級

五 如何中止一個線程呢?

stop(),suspend(),resume()(僅用於與suspend()一塊兒使用)這些方法已被棄用,因此我這裏不予講解。

5.1 使用interrupt()方法

咱們上面提到了interrupt()方法,先來試一下interrupt()方法能不能中止線程 MyThread.java

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5000000; i++) {
			System.out.println("i=" + (i + 1));
		}
	}
}
複製代碼

Run.java

public class Run {

	public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.start();
			Thread.sleep(2000);
			thread.interrupt();
		} catch (InterruptedException e) {
			System.out.println("main catch");
			e.printStackTrace();
		}
	}

}
複製代碼

運行上訴代碼你會發現,線程並不會終止

針對上面代碼的一個改進:

interrupted()方法判斷線程是否中止,若是是中止狀態則break

MyThread.java

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 500000; i++) {
			if (this.interrupted()) {
				System.out.println("已是中止狀態了!我要退出了!");
				break;
			}
			System.out.println("i=" + (i + 1));
		}
		System.out.println("看到這句話說明線程並未終止------");
	}
}
複製代碼

Run.java

public class Run {

	public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.start();
			Thread.sleep(2000);
			thread.interrupt();
		} catch (InterruptedException e) {
			System.out.println("main catch");
			e.printStackTrace();
		}
		System.out.println("end!");
	}

}
複製代碼

運行結果:

運行結果
for循環雖然中止執行了,可是for循環下面的語句仍是會執行,說明線程並未被中止。

5.2 使用return中止線程

MyThread.java

public class MyThread extends Thread {

	@Override
	public void run() {
			while (true) {
				if (this.isInterrupted()) {
					System.out.println("ֹͣ中止了!");
					return;
				}
				System.out.println("timer=" + System.currentTimeMillis());
			}
	}

}
複製代碼

Run.java

public class Run {

	public static void main(String[] args) throws InterruptedException {
		MyThread t=new MyThread();
		t.start();
		Thread.sleep(2000);
		t.interrupt();
	}

}

複製代碼

運行結果:

運行結果
固然還有其餘中止線程的方法,後面再作介紹。

六 線程的優先級

每一個線程都具備各自的優先級,線程的優先級能夠在程序中代表該線程的重要性,若是有不少線程處於就緒狀態,系統會根據優先級來決定首先使哪一個線程進入運行狀態。但這個並不意味着低 優先級的線程得不到運行,而只是它運行的概率比較小,如垃圾回收機制線程的優先級就比較低。因此不少垃圾得不到及時的回收處理。

線程優先級具備繼承特性好比A線程啓動B線程,則B線程的優先級和A是同樣的。

線程優先級具備隨機性也就是說線程優先級高的不必定每一次都先執行完。

Thread類中包含的成員變量表明瞭線程的某些優先級。如Thread.MIN_PRIORITY(常數1)Thread.NORM_PRIORITY(常數5), Thread.MAX_PRIORITY(常數10)。其中每一個線程的優先級都在Thread.MIN_PRIORITY(常數1)Thread.MAX_PRIORITY(常數10) 之間,在默認狀況下優先級都是Thread.NORM_PRIORITY(常數5)

學過操做系統這門課程的話,咱們能夠發現多線程優先級或多或少借鑑了操做系統對進程的管理。

線程優先級具備繼承特性測試代碼:

MyThread1.java:

public class MyThread1 extends Thread {
	@Override
	public void run() {
		System.out.println("MyThread1 run priority=" + this.getPriority());
		MyThread2 thread2 = new MyThread2();
		thread2.start();
	}
}

複製代碼

MyThread2.java:

public class MyThread2 extends Thread {
	@Override
	public void run() {
		System.out.println("MyThread2 run priority=" + this.getPriority());
	}
}
複製代碼

Run.java:

public class Run {
	public static void main(String[] args) {
		System.out.println("main thread begin priority="
				+ Thread.currentThread().getPriority());
		Thread.currentThread().setPriority(6);
		System.out.println("main thread end priority="
				+ Thread.currentThread().getPriority());
		MyThread1 thread1 = new MyThread1();
		thread1.start();
	}
}

複製代碼

運行結果:

運行結果

七 Java多線程分類

7.1 多線程分類

用戶線程:運行在前臺,執行具體的任務,如程序的主線程、鏈接網絡的子線程等都是用戶線程

守護線程:運行在後臺,爲其餘前臺線程服務.也能夠說守護線程是JVM中非守護線程的 「傭人」

特色:一旦全部用戶線程都結束運行,守護線程會隨JVM一塊兒結束工做

應用:數據庫鏈接池中的檢測線程,JVM虛擬機啓動後的檢測線程

最多見的守護線程:垃圾回收線程

7.2 如何設置守護線程?

能夠經過調用Thead類的setDaemon(true)方法設置當前的線程爲守護線程

注意事項:

1.  setDaemon(true)必須在start()方法前執行,不然會拋出IllegalThreadStateException異常
2. 在守護線程中產生的新線程也是守護線程
3. 不是全部的任務均可以分配給守護線程來執行,好比讀寫操做或者計算邏輯
複製代碼

MyThread.java:

public class MyThread extends Thread {
	private int i = 0;

	@Override
	public void run() {
		try {
			while (true) {
				i++;
				System.out.println("i=" + (i));
				Thread.sleep(100);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
複製代碼

Run.java:

public class Run {
	public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.setDaemon(true);
			thread.start();
			Thread.sleep(5000);
			System.out.println("我離開thread對象也再也不打印了,也就是中止了!");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
複製代碼

運行結果:

守護線程

若是你以爲博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。 歡迎關注個人微信公衆號:「Java面試通關手冊」(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取):

微信公衆號
相關文章
相關標籤/搜索