線程和多線程
程序:是一段靜態的代碼,是應用軟件執行的藍本
進程:是程序的一次動態執行過程,它對應了從代碼加載、執行至執行完畢的一個完整過程,這個過程也是進程本身從產生、發展至消亡的過程
線程:是比進程更小的執行單位。進程在其執行過程中,可以產生多個線程,形成多條執行線索,每條線索,即每個線程也有它自身的產生、存在和消亡的過程,也是一個動態的概念
主線程:(每個Java程序都有一個默認的主線程)
當JVM加載代碼發現main方法之后,就會立即啟動一個線程,這個線程稱為主線程
注意:主線程不一定是最后完成執行的線程,各個線程運行時完全獨立,爭奪cpu,很可能主線程執行完了,子進程沒有。
單線程:如果main方法中沒有創建其他的線程,那么當main方法執行完最后一個語句,JVM就會結束Java應用程序
多線程:如果main方法中又創建了其他線程,那么JVM就要在主線程和其他線程之間輪流切換,JVM要等程序中所有線程都結束之后才結束程序。
多線程的優勢:
減輕編寫交互頻繁、涉及面多的程序的困難
程序的吞吐量會得到改善
由多個處理器的系統,可以并發運行不同的線程
“同時”執行是人的感覺,在線程之間實際上輪換執行
線程生命周期(五個狀態):新建、就緒、運行、阻塞、死亡
新建狀態:線程對象已經創建,還沒有在其上調用start()方法
就緒狀態:當線程調用start方法,但調度程序還沒有把它選定為運行線程時線程
運行狀態:線程調度程序從可運行池中選擇一個線程作為當前線程時線程所處的狀態。(是線程進入運行狀態的唯一方式)
阻塞(等待/睡眠)狀態:線程仍舊是活的,但是當前沒有條件運行。當某件事件出現,他可能返回到可運行狀態
死亡狀態:當線程的run()方法完成時就認為它死去。線程一旦死亡,就不能復生。 一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常
Java中兩種創建線程的方式:
1.繼承Thread類
重寫run() 方法
new一個線程對象
調用對象的 start() 啟動線程
優點:編寫簡單,如果需要訪問當前線程直接使用this即可獲得當前線程.
缺點:因為線程類已經繼承了Thread類,不能再繼承其他的父類
?
2.實現Runnable接口
實現run() 方法
創建一個Runnable類的對象r,new MyRunnable()
創建Thread類對象并將Runnable對象作為參數,new Thread(r)
調用Thread對象的start()啟動線程
優點:線程類只實現了Runable接口,還可以繼承其他的類.
缺點:編程稍微復雜,需要訪問當前線程,必須使用Thread.currentThread()方法
?
線程創建的問題:
線程的名字:JVM給的名字或者我們自定義的名字,通過setName方法設置
獲取當前線程對象:Thread.currentThread()
在一個程序里多個線程只能保證其開始時間,而無法保證其結束時間,執行順序也無法確定
一個線程的run方法執行結束后,該線程結束
一個線程只能被啟動一次,一次只能運行一個線程
JVM線程調度程序決定實際運行哪個處于可運行狀態的線程。采用隊列形式
線程中的常用方法:
start():啟動線程,讓線程從新建狀態進入就緒隊列排隊
run():線程對象被調度之后所執行的操作
sleep():暫停線程的執行,讓當前線程休眠若干毫秒
currentThread():返回對當前正在執行的線程對象的引用
isAlive():測試線程的狀態,新建、死亡狀態的線程返回false
interrupt():“吵醒”休眠的線程,喚醒“自己”
yield():暫停正在執行的線程,讓同等優先級的線程運行
join():當前線程等待調用該方法的線程結束后,再排隊等待CPU資源
stop():終止線程
阻止線程執行的方法:
線程睡眠:(當線程睡眠時,它暫停執行,當睡眠時間到期,則返回到可運行狀態)
Thread.sleep()
使用場景:線程執行太快
需要強制設定為下一輪執行
線程睡眠是幫助其他線程獲得運行機會的最好方法
線程睡眠到期自動蘇醒,并返回到可運行狀態(不是運行狀態)
sleep()中指定的時間是線程不會運行的最短時間(sleep()方法不能保證該線程睡眠到期后就開始執行)
sleep()是靜態方法,只能控制當前正在運行的線程
?
線程的優先級
設置線程優先級:
1.線程默認優先級是創建它的執行線程的優先級
2.通過Thread實例調用setPriority()方法設置線程優先級
Thread.MIN_PRIORITY?????? (1)
Thread.NORM_PRIORITY ????(5)
Thread.MAX_PRIORITY????? (10)
通過Thread示例調用getPriority()方法得到線程優先級
?
線程讓步(yield方法 暫停當前正在執行的線程對象,并執行同等優先級的其他線程)
Thread.yieId();
yield()將導致線程從運行狀態轉到可運行狀態,有可能沒有效果無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中
線程離開運行狀態的方法:
1.調用Thread.sleep():使當前線程睡眠至少多少毫秒(盡管它可能在指定的時間之前被中斷)
2.調用Thread.yield():不能保障太多事情,盡管通常它會讓當前運行線程回到可運行性狀態,使得有相同優先級的線程有機會執行
3.調用join()方法:保證當前線程停止執行,直到調用join方法的線程完成為止。然而,如果調用join的線程沒有存活,則當前線程不需要停止
4.線程的run()方法完成
?
多線程問題——資源協調
兩個線程A和B在同時使用Stack的同一個實例對象,A正在往堆棧里push一個數據,B則要從堆棧中pop一個數據
這時,假設idx=5
如果push執行了第一行,沒執行第二行,也就是說idx并沒有+1,這時開始執行pop,彈出的元素就不對了,并且落下了一個元素。
synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:
1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;
2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;
3. 修飾一個靜態的方法,其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;
4. 修飾一個類,其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。
Synchronized的作用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改能夠及時可見(3)有效解決重排序問題。
資源同步——對象互斥鎖
關鍵字synchronized 與對象互斥鎖聯合起來使用保證對象在任意時刻只能由一個線程訪問
synchronized可以修飾方法,表示這個方法在任意時刻只能由一個線程訪問
synchronized可以修飾類,則表明該類的所有對象共用一把鎖
多線程同步模型(生產者——消費者示例)
多線程問題——死鎖
(兩個或兩個以上的線程在執行過程中,因爭奪資源而造成了互相等待)
產生死鎖的必要條件
互斥條件:指線程對所分配到的資源進行排它性使用
請求和保持條件:指線程已經保持至少一個資源,但又提出了新的資源請求
不可剝奪條件:進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放
環路等待條件:指在發生死鎖時,必然存在一個線程—資源的環形鏈
出現死鎖的情況
相互排斥:一個線程永遠占用某一共享資源
循環等待:線程A在等待線程B,線程B在等待線程C,線程C在等待線程A
部分分配:線程A得到了資源1,線程B得到了資源2,兩個線程都不能得到全部的資源
缺少優先權:一個線程訪問了某資源,但一直不釋放該資源,即使該線程處于阻塞狀態