在上一篇文章中,回顧了Java的集合。而在本篇文章中主要介紹多線程的相關知識。主要介紹的知識點爲線程的介紹、多線程的使用、以及在多線程中使用的一些方法。java
表示進程中負責程序執行的執行單元,依靠程序進行運行。線程是程序中的順序控制流,只能使用分配給程序的資源和環境。編程
表示資源的分配和調度的一個獨立單元,一般表示爲執行中的程序。一個進程至少包含一個線程。bash
線程和進程同樣分爲五個階段:建立、就緒、運行、阻塞和終止。多線程
能夠用下述圖來進行理解線程的生命週期: 併發
在瞭解了線程和進程以後,咱們再來簡單的瞭解下單線程和多線程。 單線程 程序中只存在一個線程,實際上主方法就是一個主線程。dom
多線程 多線程是指在同一程序中有多個順序流在執行。 簡單的說就是在一個程序中有多個任務運行。ide
那麼在什麼狀況下用多線程呢?ui
通常來講,程序中有兩個以上的子系統須要併發執行的,這時候就須要利用多線程編程。經過對多線程的使用,能夠編寫出高效的程序。this
那麼是否是使用不少線程就能提升效率呢?spa
不必定的。由於程序中上下文的切換開銷也很重要,若是建立了太多的線程,CPU 花費在上下文的切換的時間將多於執行程序的時間!這時是會下降程序執行效率的。
因此有效利用多線程的關鍵是理解程序是併發執行而不是串行執行的。
通常來講,咱們在對線程進行建立的時候,通常是繼承Thread 類或實現Runnable 接口。其實還有一種方式是實現 Callable接口,而後與Future 或線程池結合使用, 相似於Runnable接口,可是就功能上來講更爲強大一些,也就是被執行以後,能夠拿到返回值。
這裏咱們分別一個例子使用繼承Thread 類、實現Runnable 接口和實現Callable接口與Future結合來進行建立線程。 代碼示例: 注:線程啓動的方法是start而不是run。由於使用start方法整個線程處於就緒狀態,等待虛擬機來進行調度。而使用run,也就是看成了一個普通的方法進行啓動,這樣虛擬機不會進行線程調度,虛擬機會執行這個方法直到結束後自動退出。
代碼示例:
public class Test {
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
threadTest.start();
RunalbeTest runalbeTest=new RunalbeTest();
Thread thread=new Thread(runalbeTest);
thread.start();
CallableTest callableTest=new CallableTest();
FutureTask<Integer> ft = new FutureTask<Integer>(callableTest);
Thread thread2=new Thread(ft);
thread2.start();
try {
System.out.println("返回值:"+ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("這是一個Thread的線程!");
}
}
class RunalbeTest implements Runnable{
@Override
public void run() {
System.out.println("這是一個Runnable的線程!");
}
}
class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("這是一個Callable的線程!");
return 2;
}
}
複製代碼
運行結果:
這是一個Thread的線程!
這是一個Runnable的線程!
這是一個Callable的線程!
返回值:2
複製代碼
經過上述示例代碼中,咱們發現使用繼承 Thread 類的方式建立線程時,編寫最爲簡單。而使用Runnable、Callable 接口的方式建立線程的時候,須要經過Thread類的構造方法Thread(Runnable target) 構造出對象,而後調用start方法來運行線程代碼。順便說下,其實Thread類實際上也是實現了Runnable接口的一個類。
可是在這裏,我推薦你們建立單線程的時候使用繼承 Thread 類方式建立,多線線程的時候使用Runnable、Callable 接口的方式來建立建立線程。 至於爲何呢?在下面中的描述已給出理由。
總的來講就是,單線程建議用繼承 Thread 類建立,多線程建議- 使用Runnable、Callable 接口的方式建立。
使用yield方法表示暫停當前正在執行的線程對象,並執行其餘線程。
代碼示例:
public class YieldTest {
public static void main(String[] args) {
Test1 t1 = new Test1("張三");
Test1 t2 = new Test1("李四");
new Thread(t1).start();
new Thread(t2).start();
}
}
class Test1 implements Runnable {
private String name;
public Test1(String name) {
this.name=name;
}
@Override
public void run() {
System.out.println(this.name + " 線程運行開始!");
for (int i = 1; i <= 5; i++) {
System.out.println(""+this.name + "-----" + i);
// 當爲3的時候,讓出資源
if (i == 3) {
Thread.yield();
}
}
System.out.println(this.name + " 線程運行結束!");
}
}
複製代碼
執行結果一:
張三 線程運行開始!
張三-----1
張三-----2
張三-----3
李四 線程運行開始!
李四-----1
李四-----2
李四-----3
張三-----4
張三-----5
張三 線程運行結束!
李四-----4
李四-----5
李四 線程運行結束!
複製代碼
執行結果二:
張三 線程運行開始!
李四 線程運行開始!
李四-----1
李四-----2
李四-----3
張三-----1
張三-----2
張三-----3
李四-----4
李四-----5
李四 線程運行結束!
張三-----4
張三-----5
張三 線程運行結束!
複製代碼
上述中的例子咱們能夠看到,啓動兩個線程以後,哪一個線程先執行到3,就會讓出資源,讓另外一個線程執行。 在這裏順便說下,yield和sleep的區別。
使用join方法指等待某個線程終止。也就是說當子線程調用了join方法以後,後面的代碼只有等待該線程執行完畢以後纔會執行。
若是很差理解,這裏依舊使用一段代碼來進行說明。 這裏咱們建立兩個線程,並使用main方法執行。順便提一下,其實main方法也是個線程。若是直接執行的話,可能main方法執行完畢了,子線程還沒執行完畢,這裏咱們就讓子線程使用join方法使main方法最後執行。
代碼示例:
public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "主線程開始運行!");
Test2 t1=new Test2("A");
Test2 t2=new Test2("B");
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主線程運行結束!");
}
}
class Test2 extends Thread{
public Test2(String name) {
super(name);
}
public void run() {
System.out.println(this.getName() + " 線程運行開始!");
for (int i = 0; i < 5; i++) {
System.out.println("子線程"+this.getName() + "運行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 線程運行結束!");
}
}
複製代碼
執行結果:
main主線程開始運行!
B 線程運行開始!
子線程B運行 : 0
A 線程運行開始!
子線程A運行 : 0
子線程A運行 : 1
子線程B運行 : 1
子線程B運行 : 2
子線程B運行 : 3
子線程B運行 : 4
B 線程運行結束!
子線程A運行 : 2
子線程A運行 : 3
子線程A運行 : 4
A 線程運行結束!
main主線程運行結束!
複製代碼
上述示例中的結果顯然符合咱們的預期。
使用setPriority表示設置線程的優先級。 每一個線程都有默認的優先級。主線程的默認優先級爲Thread.NORM_PRIORITY。 線程的優先級有繼承關係,好比A線程中建立了B線程,那麼B將和A具備相同的優先級。 JVM提供了10個線程優先級,但與常見的操做系統都不能很好的映射。若是但願程序能移植到各個操做系統中,應該僅僅使用Thread類有如下三個靜態常量做爲優先級,這樣能保證一樣的優先級採用了一樣的調度方式
可是設置優先級並不能保證線程必定先執行。咱們能夠經過一下代碼來驗證。
代碼示例:
public class PriorityTest {
public static void main(String[] args) {
Test3 t1 = new Test3("張三");
Test3 t2 = new Test3("李四");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
class Test3 extends Thread {
public Test3(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " 線程運行開始!");
for (int i = 1; i <= 5; i++) {
System.out.println("子線程"+this.getName() + "運行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 線程運行結束!");
}
}
複製代碼
執行結果一:
李四 線程運行開始!
子線程李四運行 : 1
張三 線程運行開始!
子線程張三運行 : 1
子線程張三運行 : 2
子線程李四運行 : 2
子線程李四運行 : 3
子線程李四運行 : 4
子線程張三運行 : 3
子線程李四運行 : 5
李四 線程運行結束!
子線程張三運行 : 4
子線程張三運行 : 5
張三 線程運行結束!
複製代碼
執行結果二:
張三 線程運行開始!
子線程張三運行 : 1
李四 線程運行開始!
子線程李四運行 : 1
子線程張三運行 : 2
子線程張三運行 : 3
子線程李四運行 : 2
子線程張三運行 : 4
子線程李四運行 : 3
子線程張三運行 : 5
子線程李四運行 : 4
張三 線程運行結束!
子線程李四運行 : 5
李四 線程運行結束!
複製代碼
執行結果三:
李四 線程運行開始!
子線程李四運行 : 1
張三 線程運行開始!
子線程張三運行 : 1
子線程李四運行 : 2
子線程李四運行 : 3
子線程李四運行 : 4
子線程張三運行 : 2
子線程張三運行 : 3
子線程張三運行 : 4
子線程李四運行 : 5
子線程張三運行 : 5
李四 線程運行結束!
張三 線程運行結束!
複製代碼
線程中還有許多方法,可是這裏並不會所有細說。只簡單的列舉了幾個方法使用。更多的方法使用能夠查看相關的API文檔。這裏我也順便總結了一些關於這些方法的描述。
其實這篇文章好久以前都已經打好草稿了,可是因爲各類緣由,只到今天才寫完。雖然也只是簡單的介紹了一下多線程的相關知識,也只能算個入門級的教程吧。不過寫完以後,感受本身又從新複習了一遍多線程,對多線程的理解又加深了一些。 話已盡此,不在多說。 原創不易,若是感受不錯,但願給個推薦!您的支持是我寫做的最大動力!
參考:https://blog.csdn.net/evankaka/article/details/44153709#t1