線程:進程中負責程序執行的執行單元。一個進程中至少有一個線程。java
多線程:一個進程中包含有多個線程,但CPU在同一時間只容許一個線程的進行。因此有多個線程的運行是根據CPU切換完成,如何切換由CPU決定,所以多線程運行具備不肯定性。bash
即建立一個thread類,或者建立一個thread子類的對象多線程
1>Thread類的介紹ui
Thread類是一個線程類,位於java.lang包下this
Thread的主要構造方法:spa
1.1 thread() 建立一個線程對象操作系統
1.2 thread(String name) 建立一個具備指定名稱的線程對象線程
1.3 thread(Runnable target) 建立一個基於runnable接口實現類的線程對象code
1.4 thread(Runnable target,string name) 建立一個基於Runnable接口實現類,而且具備指定名稱的線程對象cdn
經常使用方法: 1.1 public void run() 線程相關的代碼寫在該方法中,通常須要重寫
1.2 public void start() 啓動線程
1.3 public static void sleep(long m) 線程休眠m毫秒
1.4 public final void join() 優先執行調用join方法的線程
2>具體的代碼實現
package _Thread;
class mythread extends Thread{
//重寫Run方法
public void run() {
System.out.println(getName()+"線程");
}
}
public class _threadTest {
public static void main(String[] args) {
mythread sr=new mythread();
sr.start;//啓動線程
}
}
複製代碼
實現結果
而後爲了將CPU時間片的輪轉體現出來,將加入2個循環以下:
package _Thread;
class mythread extends Thread{
public void run() {
for(int i=1;i<=10;i++)
System.out.println(getName()+"線程"+i+"次");
}
}
public class _threadTest {
public static void main(String[] args) {
mythread sr=new mythread();
sr.start();
for(int i=1;i<=10;i++)
System.out.println("主線程"+i+"次");
}
}
複製代碼
兩次不一樣的運行結果:
這說明線程的運行是有cpu隨機切換完成的,具備不肯定性。
tips:
線程只能啓動一次
主方法也是一個線程
1>Runnable類的介紹
因爲JAVA是不支持多繼承的,因此若是一個類已經有一個父類,那麼咱們就沒法經過繼承Threadl類來建立線程,此時咱們就能夠經過實現Runnable接口來完成。Runnable是java中實現線程的接口,它有且只有一個方法Run(),而且任何實現線程功能的類必須實現該接口。
2>具體的代碼實現
package _Thread;
class mythread implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"線程");
/*
因爲這裏沒有繼承Thread類中的方法,
因此這裏調用Thread中的currentThread()的getName()來獲取當前線程的名字
*/
}
}
public class _threadTest {
public static void main(String[] args) {
mythread sr=new mythread();
Thread pr=new Thread(sr);//經過調用Thread類中的含參構造方法來建立一個線程對象
pr.start();
}
}
複製代碼
tips:一個實例能夠被多個線程所共享,通常用於多個線程處理同一個資源的狀況。
1.1新建(NEW)狀態
複製代碼
當程序使用new關鍵字建立了一個線程以後,該線程就處於新建狀態.
1.2可運行(Runnable)狀態
複製代碼
該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。
·關於直接調用run()方法
若是直接調用線程對象的run方法,也就是至關於啓動線程,並直接執行run方法,並且再run方法結束以前沒法運行其餘線程。
1.3正在運行(Runnming)狀態
複製代碼
當CPU開始調度處於可運行狀態的線程時,此時線程得到了CPU的時間片才得以真正開始執行run()方法的線程執行體,則該線程處於可運行狀態。
1.4阻塞(Blocked)狀態
複製代碼
處於運行狀態的線程在某些狀況下,讓出CPU並暫時中止該線程的運行,也就是進入了阻塞狀態。
1.5終止(Dead)狀態
複製代碼
線程的run()或call()方法執行完成,線程會正常結束;
線程拋出了一個未捕獲的Exception或Errorl異常,線程會意外終止;
或者直接調用該線程stop()方法來結束該線程,但可能會致使線程的死鎖;
線程的生命週期其實是指線程的幾種狀態的之間的切換。
生命週期圖以下:
這些狀態的切換每每是經過調用Thread類中的方法來實現的,下面介紹幾種經常使用的方法:2.1 public static native void sleep(long millis)
複製代碼
2.2 object.wait()
複製代碼
·對象鎖簡單的理解就是在代碼中的方法上加了synchronized的鎖,或者synchronized(this)的代碼段 於之對應的還有類鎖,指在代碼中的方法上加了static和synchronized的鎖(在下面還會詳細涉及到)。
2.3 Thread.yield()
複製代碼
2.4 Thread.join()
複製代碼
會優先將該線程運行完畢,再去調用其餘線程,也就是等待該線程的終止
其重載方法還有
public final void join(long millis)等待該線程終止的時間最長爲 millis 毫秒
public final void join(long millis, int nanos)等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒
Java 把線程優先級分紅10個級別(依次爲1到10,數字越大線程的優先級越高),線程被建立時若是沒有明確聲明則使用默認優先級(5)。雖然優先級越高越先被執行,但咱們不能用線程優先級來規定線程的執行順序。由於線程的執行順序還受不少其餘因素影響,例如操做系統和JVM的調度等等,因此咱們只能說線程優先級越大,越先被執行的可能性越高。這裏也體現了CPU調度的隨機性。
最小優先級(1)=Thread.MIN_PRIORITY
默認優先級(5)=Thread.NORM_PRIORITY
最大優先級(10)=Thread.MAX_PRIORITY
2.1 public int getPriority()
複製代碼
2.2 public void setPriority(int newPriority)
複製代碼
因爲線程被執行的時間、佔用cpu的時間都是不肯定的,因此在有多個線程要被執行的狀況下,會產生許多問題。例,在有兩個線程(線程1和線程2)對同一個類的屬性進行操做時,若是線程1對該屬性進行「加」的操做前,線程2忽然搶佔了CPU來對該屬性進行了「減」的操做,對該屬性進行修改,那麼就可能出現相似於1+1=1的結果。
爲了不上述結果的出現,這裏須要調用一個synchronized關鍵字(同步),來對該共享對象進行上鎖,即讓該共享對象在同一個時刻,只能被一個線程訪問。
通常加在線程前,代表該線程不容許被打斷, 能夠加在靜態方法/方法/語句塊前。 例:
public synchronized void 方法名(){}
複製代碼
public static synchronized void 方法名(){}
複製代碼
synchronized(對象名){}
複製代碼
咱們先引進三個方法
wait()中斷方法的執行,使線程等待
複製代碼
notify()隨機喚醒等待的某一個線程
複製代碼
notifyall()喚醒全部處於等待的線程
複製代碼
對多線程的處理,下面以對2個線程的處理爲例
通常是將公共對象看成一個容器queue,並在該容器內設置一個Boolean類型的值,在將線程1和2設置爲synchronized類型後,在線程1中添加判斷語句,若是該Boolean型值爲ture(也能夠是flase)才能執行該線程的操做語句,不然進入等待(在這裏調用wait()使其暫停運行,記得要在線程最後改變Boolean類型值的值),對線程2的操做也相似,這樣就能解決對線程調度順序問題。
但這樣問題並非徹底被解決,若是線程1和2都進入休眠狀態,那麼鎖死,即兩個線程都不進行。因此咱們還須要在線程1和線程2的最後加上喚醒醒另外一個線程的語句(這裏調用notifyall(),之因此不調用notify()是由於它是隨機喚醒某一個線程,若是在程序中還有線程三、線程4···,那咱們想喚醒的線程有可能不被喚醒)