多線程的實現(xiàn)方式
繼承Thread類。實現(xiàn)Runnable接口。Callable接口配合Executors線程池,可以獲取線程執(zhí)行結果,用Future類接收。
Callable&Future例子:
public class CallableTest implements Callable<Integer> {
private int a;
private int b;
private void cat(){
for (int i = 0 ;i<a;i++){
b += a;
}
}
public CallableTest(int a) {
this.a = a;
}
@Override
public Integer call() throws Exception {
cat();
return b;
}
public static void main(String[] args) throws Exception{
CallableTest callableTest = new CallableTest(100);
FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest);
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(futureTask);
System.out.printf(futureTask.get().toString());
}
}
線程的6個狀態(tài)
- 初始(NEW):新創(chuàng)建了一個線程對象,但還沒有調用start()方法。
- 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運行”。
線程對象創(chuàng)建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處于就緒狀態(tài)(ready)。就緒狀態(tài)的線程在獲得CPU時間片后變?yōu)檫\行中狀態(tài)(running)。 - 阻塞(BLOCKED):表示線程阻塞于鎖。
- 等待(WAITING):進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)。
- 超時等待(TIMED_WAITING):該狀態(tài)不同于WAITING,它可以在指定的時間后自行返回。
-
終止(TERMINATED):表示該線程已經執(zhí)行完畢。
image.png
1.Thread.sleep(long millis),一定是當前線程調用此方法,當前線程進入TIMED_WAITING狀態(tài),但不釋放對象鎖,millis后線程自動蘇醒進入就緒狀態(tài)。作用:給其它線程執(zhí)行機會的最佳方式。
2.Thread.yield(),一定是當前線程調用此方法,當前線程放棄獲取的CPU時間片,但不釋放鎖資源,由運行狀態(tài)變?yōu)榫途w狀態(tài),讓OS再次選擇線程。作用:讓相同優(yōu)先級的線程輪流執(zhí)行,但并不保證一定會輪流執(zhí)行。實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中。Thread.yield()不會導致阻塞。該方法與sleep()類似,只是不能由用戶指定暫停多長時間。
3.thread.join()/thread.join(long millis),當前線程里調用其它線程t的join方法,當前線程進入WAITING/TIMED_WAITING狀態(tài),當前線程不會釋放已經持有的對象鎖。線程t執(zhí)行完畢或者millis時間到,當前線程進入就緒狀態(tài)。
4.obj.wait(),當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout) timeout時間到自動喚醒。
5.obj.notify()喚醒在此對象監(jiān)視器上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象監(jiān)視器上等待的所有線程。
6.LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 當前線程進入WAITING/TIMED_WAITING狀態(tài)。對比wait方法,不需要獲得鎖就可以讓線程進入WAITING/TIMED_WAITING狀態(tài),需要通過LockSupport.unpark(Thread thread)喚醒。
線程的終止interrupt
在java多線程中提供了stop方法用來停止一個線程,但是這種做法非常暴力,它會直接終止一個線程,會影響子線程中的代碼邏輯,而有一種更優(yōu)雅的方式就是中斷interrupt。
中斷在java中主要有3個方法,interrupt(),isInterrupted()和interrupted()。
·interrupt(),在一個線程中調用另一個線程的interrupt()方法,即會向那個線程發(fā)出信號——線程中斷狀態(tài)已被設置。隨后可以根據這個信號來執(zhí)行相應的代碼邏輯,如要結束線程可以直接return。注意:當子線程處于等待或超時等待狀態(tài)時,調用此方法會拋出InterruptException異常,可以使子線程的中斷標志重置為false,并讓子線程從等待或超時等待狀態(tài)中脫離。
·isInterrupted(),用來判斷當前線程的中斷狀態(tài)(true or false)。
·interrupted()是個Thread的static方法,用來恢復中斷狀態(tài)。
也可以手動設置一個boolean變量,使用volatile修飾,這樣在主線程中修改這個boolean變量值,子線程也可以根據這個值來執(zhí)行相應的代碼邏輯使線程執(zhí)行完畢進入終止狀態(tài)。
線程的安全性問題
可見性、原子性、有序性問題(編譯器重排,cpu重排,內存重排)
可見性:一個線程對主內存的修改可以及時的被其他線程觀察到。就是在多個線程同時運行時,如果都涉及到對某個共享變量的使用,能夠第一時間知道這個變量的值變化。
原子性:即一個線程對共享變量的操作必須是原子操作。多線程環(huán)境下,從其執(zhí)行線程以外的任何線程來看,執(zhí)行線程對共享變量的操作是不可分割的,即要么全部成功要么全部失敗。
有序性問題:分為三種:編譯器重排,cpu重排,內存重排。JVM虛擬機對代碼進行編譯時可能會改變一些代碼的執(zhí)行順序,但會保證最后的執(zhí)行結果和預期的結果是一致的。如果不存在數據依賴性, CPU可以改變代碼對應的機器指令的順序使之更有效率地執(zhí)行。因為cpu有著一個高速緩存架構,多核cpu在通過緩存對主內存讀寫時, 可能會使得加載和存儲操作看上去可能是在亂序執(zhí)行。
