標籤:div 建立 method run方法 沒有 status 並發編程 方法 ati
1、線程相關概念
1)、線程與進程的區別
線程是作業系統調度的最小單元,也叫輕量級進程,而進程是作業系統中的應用程式,在進程中可以建立多個線程。
2)、環境切換
我們知道現代處理器都是多核的,幾核處理器只能同時處理幾個線程,多線程執行程式看起來是同時進行,實際上是CPU在多個線程之間快速切換執行,這中間就涉及到上下問切換,所謂的環境切換就是指一個線程T被分配的時間片用完了之後,線程的資訊被儲存起來,CPU執行另外的線程,再到CPU讀取線程T的資訊並繼續執行T的過程。
2、線程實現方式
1)、繼承Thread類
由於類的單繼承性,使用這種方式實現就無法再去繼承其他的線程,有局限,耦合度高。
public class MyThread extends Thread{ @Override public void run() { super.run(); System.out.println("我繼承了Thread類..."); }}
2)、實現Runnable介面
大多數都用這種方式實現,很靈活,重寫run()方法即可,run() 方法沒有傳回值。
3)、實現Callable介面
若是想擷取到線程的執行結果,那就用這種方式,它和實現Runnable介面的區別是要重寫call()方法,call()方式是有傳回值的,返回的Object是任務的執行結果,可以用Future介面的實作類別FutureTask來接收,並調用get()方法擷取到執行結果。另外call()方法可拋出異常,而run()方法是不能拋出異常的
public class Test { public static void main(String[] args) throws Exception { Thread thread1 = new MyThread(); thread1.start(); Thread thread2 = new Thread(new MyRunnable()); thread2.start(); Callable callable = new MyCallable(); FutureTask<String> future = new FutureTask<>(callable); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(future); System.out.println(future.get()); executorService.shutdown(); } static class MyRunnable implements Runnable{ @Override public void run() { System.out.println("我實現了Runnable介面..."); } } static class MyCallable implements Callable{ @Override public String call() throws Exception { return "我實現了Callable介面..."; } }}
執行結果:
我繼承了Thread類...
我實現了Runnable介面...
我實現了Callable介面...
3、線程狀態
根據jdk中Thread類的State內部類,線程有6種狀態,下次面試官問你線程有幾種狀態,你可以很有底氣的回答:6種,如下左圖,右圖是線程狀態之間的轉換。
這裡注意:線程在等待進入synchronzed方法或者synchronized塊時的線程狀態時BLOCKED,而在等待進入lock鎖時的狀態是WAITING或者TIME_WAITING,因為lock是用LockSupport實現的(源碼還沒研究)。
測試如下:
public class MyService { public static synchronized void serviceMethod1(){
try{
System.out.println(Thread.currentThread().getName+"進入了業務方法");
Thread.sleep(millis: 1000);
} catch (Execption e){
e.printStackTrace();
}
}
public static void serviceMethod2(){
ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); System.out.println(Thread.currentThread().getName()+"進入了業務方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock.unlock();
}
}
public class MyThread1 extends Thread{ @Override public void run() { MyService.serviceMethod1();}public class MyThread2 extends Thread{ @Override public void run() { MyService.serviceMethod1(); }}public class MyThread3 extends Thread{ @Override public void run() { MyService.serviceMethod2();}}public class MyThread4 extends Thread{ @Override public void run() { MyService.serviceMethod2(); }}public class ThreadStatusTest { public static void main(String[] args){ test1(); test2(); } public static void test1(){ MyThread1 t1 = new MyThread1(); t1.setName("a"); t1.start(); MyThread2 t2 = new MyThread2(); t2.setName("b"); t2.start(); System.out.println("t2的狀態:"+t2.getState()); } public static void test2(){ MyThread3 t3 = new MyThread3(); t3.setName("a"); t3.start(); MyThread4 t4 = new MyThread4(); t4.setName("b"); t4.start(); System.out.println("t4的狀態:"+t4.getState()); }}
test1() 運行結果:
a進入了業務方法
t2的狀態:RUNNABLE
b進入了業務方法
test2() 運行結果:
a進入了業務方法
t4的狀態:RUNNABLE
b進入了業務方法
4、常用方法
1)、start() 啟動線程的一種方式,線程調用start()方法後由NEW狀態變為RUNNABLE狀態中的READY狀態,等待CPU分配時間片,它是Thread類的方法;
2)、 run() 所需實現的邏輯寫在run方法裡,線程獲得時間片後由READY狀態變為RUNNING,並自動調用run()方法,當然run()方法也可以直接調用,那它就是普通方法,和線程無關;
3)、sleep(...) 線程由RUNNABLE狀態變為TIMED_WAITING狀態,調用此方法會拋出InterruptedException異常,線程自己擁有系統資源,並等待時間到了,自己醒來,這是和wait()方法的主要區別;
4)、wait() notify() notifyAll() 這三個都是Object類的方法,它們是配合使用的,調用wait()方法的線程狀態由RUNNABLE中的RUNNING變為WAITING狀態,並且此對象是不佔有系統資源的,當調用notify()或notifyAll() 方法後線程又進入RUNNABLE中的READY狀態,等待擷取CPU時間片;
5)、join(...) 等待線程對象銷毀,線程狀態由RUNNABLE變為WAITING或者TIMED_WAITING;
6)、interrupt() 中斷線程,線程狀態變為TERMINATED;
7)、yield() ,線程狀態由RUNNING變為READY,即由運行中變為就緒狀態,向處理器表示自己願意放棄當前CPU資源(讓出自己的執行時間),但放棄時間不確定,有可能剛剛放棄,馬上又獲得CPU時間片,所有此方法並不能保證其它線程一定執行,調用此方法的線程一定不執行,而是看CPU是否分配了時間片,並且它只會讓優先順序不低於當前線程的線程執行,優先順序比它低的是沒有機會執行的。
5、線程的優先順序
線程優先順序為1-10(由低到高),預設優先順序是5,優先順序高的線程分配的時間片的數量要低於優先順序低的,我們可以調用Thread.setPriority(int) 方法來為線程設定優先權,在設定線程優先順序時應注意,爭對頻繁阻塞的,如休眠、IO、資料庫等任務的線程應設定較高的優先順序,對於偏重計算的,如需要較多的CPU時間或者偏運算的線程則應設定較低的優先順序,確保處理器不會被獨佔。
6、線程間的通訊
1)、volatile synchronized
這兩個關鍵字可以實現線程間的通訊,我們知道每個線程都有自己的工作記憶體,並且它們還有共用記憶體,線程對一個變數修改時會先從共用記憶體中讀取這個變數到自己私人的工作記憶體中,若是一個普通變數則修改後重新整理到主記憶體中的時機時隨機的,若是volatile變數(可見度和有序性),這時另一個線程來讀這個變數,則它會被立即重新整理到主記憶體中去,讓後面讀取的線程能看到變化,這就實現了兩個線程之間的通訊。
synchronized實現線程之間的同步,B線程必須等到A線程釋放鎖才能獲得相應的資源,這是線程之間的一種通訊方式。
2)、等待通知機制 wait() notify() notifyAll()
一個過程從一個線程開始,在另一個線程結束,前者是生產者,後者是消費者,產生者完成生產,通知消費者去消費,完成二者之間的通訊。
等待通知的相關方法有
wait():調用該方法的線程進入WAITING狀態,只有被其他線程通知或者被中斷才會返回,調用該方法後,會釋放對象的鎖;
wait(long):逾時等待一段時間,這裡的參數時間是毫秒,也就是等待長達n毫秒,如果沒有通知就逾時返回;
wait(long,int):逾時時間為long毫秒+int納秒;
notify():通知一個在對象等待的線程,使其從wait()方法返回,而返回的前提時該線程擷取了對象的鎖;
notifyAll():通知所有等待在該對象上的線程。
以上方法都來自java.lang.Object類中,所以只要是對象就可以調用它們。
wait()、notify()和notifyAll()調用時需要注意:
a、使用wait()、notify()和notifyAll()時需要先對調用對象加鎖;
b、調用wait()方法後,線程狀態由RUNNING變為WAITING,並將當前線程放置到對象的等待隊列;
c、notify()或者notifyAll()方法調用後,等待線程依舊不會從wait()返回,需要調用notify()或者notifyAll()的線程釋放鎖之後,等待線程才有機會從wait()返回;
d、notify()方法將等待隊列中的一個等待線程從等待隊列中移到同步隊列中,而notifyAll()方法將等待隊列中所有的線程全部移到同步隊列,被移動的線程狀
態由WAITING變為BLOCKED;
e、從wait()方法返回的前提是獲得了調用對象的鎖。
A線程調用wait() 方法會釋放持有的對象監視器,進入等待狀態,等B線程執行完了後,調用notify()或者notifyAll() 方法喚醒A線程
3)、管道輸入、輸出資料流
管道流專門用於線程之間的通訊,和一般字元位元組流的區別是它們操作的是記憶體而不是硬碟。
主要有四種實現:位元組流:PipedOutputStream、PipedInputStream
字元流:PipedWriter、PipedReader
4)、join(...)
當前存線上程A、B,若A執行了join()方法,意思就是:當前線程A等待B線程執行完成之後,才繼續執行,即完成了A、B間的通訊。
5)、ThreadLocal
ThreadLocal,即線程變數,是一個以ThreadLocal對象為鍵,任意對象為值的儲存結構。可以通過set(T)方法來設定一個值,在當前線程下通過get()方法擷取原先擷取的值。
下面摘抄《Java並發編程的藝術》中的一段代碼:
public class Profiler { private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){ protected Long initialValue(){ return System.currentTimeMillis(); } }; public static final void begin(){ TIME_THREADLOCAL.set(System.currentTimeMillis()); } public static final long end(){ return System.currentTimeMillis() - TIME_THREADLOCAL.get(); } public static void main(String[] args) throws InterruptedException { Profiler.begin(); TimeUnit.SECONDS.sleep(1); System.out.println("Cost: "+Profiler.end() +" mills"); }}
運行結果:
Cost: 1005 mills
參考資料:《Java並發編程的藝術》
最後,如有寫的不對或不好的地方,請指出,謝謝!
Java多線程學習總結之---多線程基礎