最近聽很多面試的小夥伴說,網上往往是一篇一篇的Java多線程的文章,除了書籍沒有什麼學習多線程的一系列文章。但是僅僅憑藉一兩篇文章很難對多線程有系統的學習,而且面試的時候多線程這方面的知識往往也是考察的重點,所以考慮之下決定寫一系列關於Java多線程的文章。文章參考了高老師的《Java多線程編程核心技術》。力爭使用最短的篇幅把Java多線程的知識作以系統的講述。
本節思維導圖:
思維導圖源文件+思維導圖軟件關注微信公衆號:“Java團長”回覆關鍵字:“Java多線程”即可免費領取。
一 進程和多線程簡介
1.1 相關概念
何爲線程?
線程與進程相似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因爲如此,線程也被稱爲輕量級進程。
何爲進程?
進程是程序的一次執行過程,是系統運行程序的基本單位,因此進程是動態的。系統運行一個程序即是一個進程從創建,運行到消亡的過程。簡單來說,一個進程就是一個執行中的程序,它在計算機中一個指令接着一個指令地執行着,同時,每個進程還佔有某些系統資源如CPU時間,內存空間,文件,文件,輸入輸出設備的使用權等等。換句話說,當程序在執行時,將會被操作系統載入內存中。
線程和進程有何不同?
線程是進程劃分成的更小的運行單位。線程和進程最大的不同在於基本上各進程是獨立的,而各線程則不一定,因爲同一進程中的線程極有可能會相互影響。從另一角度來說,進程屬於操作系統的範疇,主要是同一段時間內,可以同時執行一個以上的程序,而線程則是在同一程序內幾乎同時執行一個以上的程序段。
1.2 多線程
何爲多線程?
多線程就是幾乎同時執行多個線程(一個處理器在某一個時間點上永遠都只能是一個線程!即使這個處理器是多核的,除非有多個處理器才能實現多個線程同時運行。)。幾乎同時是因爲實際上多線程程序中的多個線程實際上是一個線程執行一會然後其他的線程再執行,並不是很多書籍所謂的同時執行。
爲什麼多線程是必要的?
- 使用線程可以把佔據長時間的程序中的任務放到後臺去處理
- 用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
- 程序的運行速度可能加快
二 使用多線程
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--的操作分爲如下下三步:
- 取得原有count值
- 計算i -1
- 對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();
}
}
}
運行結果:
如果你覺得博主的文章不錯,歡迎轉發點贊。