一:基本概念:
- 1:什么叫進(jìn)程以及線程
進(jìn)程:是一個具有獨(dú)立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運(yùn)行活動。它可以申請和擁有系統(tǒng)資源,是一個動態(tài)的概念,是一個活動的實(shí)體。它不只是程序的代碼茫舶,還包括當(dāng)前的活動,通過程序計(jì)數(shù)器的值和處理寄存器的內(nèi)容來表示刹淌。進(jìn)程就是我們電腦上運(yùn)行的一個程序例如:QQ;
線程:就是進(jìn)程的一個分支饶氏,一個進(jìn)程可以有多個線程,線程是不能擁有資源的有勾,只有一些運(yùn)行時(shí)必要的數(shù)據(jù)結(jié)構(gòu)疹启;每一個線程都會擁有一個父進(jìn)程,進(jìn)程擁有電腦或者程序分配的一些資源蔼卡,這些資源可以被進(jìn)程下面的線程所共享喊崖;
通常在一個進(jìn)程中可以包含若干個線程,它們可以利用進(jìn)程所擁有的資源雇逞。在引入線程的操作系統(tǒng)中荤懂,通常都是把進(jìn)程作為分配資源的基本單位,而把線程作為獨(dú)立運(yùn)行和獨(dú)立調(diào)度的基本單位塘砸。由于線程比進(jìn)程更小节仿,基本上不擁有系統(tǒng)資源,故對它的調(diào)度所付出的開銷就會小得多掉蔬,能更高效的提高系統(tǒng)內(nèi)多個程序間并發(fā)執(zhí)行的程度廊宪。
多線程程序是一種多任務(wù)多并發(fā)的程序;具有以下優(yōu)點(diǎn): - A:提高應(yīng)用程序的相應(yīng):例如:當(dāng)一個程序耗時(shí)很長時(shí)眉踱,如果使用單線程挤忙,此時(shí)電腦將無法為其他事件作出響應(yīng);而多線程則可以為這個耗時(shí)很長的程序單獨(dú)開辟一個線程讓其自己去完成谈喳,為其他操作開辟另外一個線程,即使響應(yīng)相關(guān)的操作戈泼;
- B:使CPU系統(tǒng)更加的有效婿禽。當(dāng)一個電腦上運(yùn)行有多個CPU時(shí),操作系統(tǒng)就會保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí)大猛;不同的線程運(yùn)行在不同的CPU上扭倾;
- C:改善程序的結(jié)構(gòu);將一個復(fù)雜的進(jìn)程分解為多個線程挽绩;使每一個線程成為獨(dú)立或者半獨(dú)立的狀態(tài)膛壹;有利于程序的維護(hù)或者修改
2:多進(jìn)程和多線程
多進(jìn)程是指一個電腦同時(shí)運(yùn)行多個任務(wù)的狀態(tài)
多線程是指同一個程序(任務(wù))中多個順序流在執(zhí)行;
二:Java中的多線程:
-
1:實(shí)現(xiàn)方式:
- 1:繼承Thread類
- 2:實(shí)現(xiàn)Runnable接口
- 3:通過Callable 和 Future 創(chuàng)建線程
1:集成Thread類
public class test{
public static void main(String[] arg){
thread1 t1 = new thread1();
t1.start();
}
}
class thread1 extends Thread
public void run(){
System.out.println("我是繼承了Thread類實(shí)現(xiàn)的多線程!");
}
}
B: 實(shí)現(xiàn)了Runnable接口的多線程
public class test{
public static void main(String[] arg){
runnable r = new runnable();
thread1 t1 = new thread1(r);
t1.start();
}
}
class runnable1 implements Runnable{
public void run() {
System.out.println("我是實(shí)現(xiàn)了Runable接口的多線程模聋!");
}
}
C:通過實(shí)現(xiàn)Callable接口并結(jié)構(gòu)FutureTask創(chuàng)建線程肩民,他和繼承Thread線程之間不會共享數(shù)據(jù);
class callableThread implements Callable<Integer>{
private int temp = 10;
@Override
public Integer call() throws Exception {
for(int i=0;i<10;i++){
temp--;
System.out.println(Thread.currentThread().getName()+"="+i);
}
return temp;
}
}
public class test {
public static void main(String[] args) {
callableThread ct = new callableThread();
FutureTask<Integer> ft = new FutureTask<>(ct);
FutureTask<Integer> ft1 = new FutureTask<>(ct);
new Thread(ft,"線程1").start();
new Thread(ft1,"線程2").start();
}
}
兩種方式的優(yōu)缺點(diǎn):
實(shí)現(xiàn)Runnable接口可以避免java的單繼承的缺點(diǎn)链方;
實(shí)現(xiàn)Runnable接口多線程操作下可以保證線程安全持痰;即實(shí)現(xiàn)Runable接口的對象可以被多個線程共享;進(jìn)而實(shí)現(xiàn)變量的共享祟蚀;適合于多個線程處理同一資源的情況工窍;
三:中斷線程:
每一個線程都會有一個標(biāo)志位用來判斷該線程是否被終止;所以每次調(diào)用之前我們都應(yīng)該先檢查該線程該標(biāo)志位判斷該線程是否終止前酿;
當(dāng)線程中的run方法執(zhí)行到最后一句并經(jīng)由rerturn語句返回時(shí)患雏,或者程序中存在沒有捕獲的異常時(shí)線程將終止;
調(diào)用interrup方法可以用來終止線程將其標(biāo)志置位罢维;
isInterrupt方法也可以用來判斷線程是否終止淹仑,并且它也不會改變線程的狀態(tài);另外一個Interrupted方法也具有該功能言津,但是它會改變該線程的狀態(tài)攻人;
四:線程的狀態(tài)以及各個狀態(tài)之間的轉(zhuǎn)換:
- A:注意yield方法只允許比自己優(yōu)先級高的線程或者和自己的優(yōu)先級相同的線程調(diào)用;
- B:join方法是一個線程的方法悬槽,例如:一個程序啟動了A和B兩個線程怀吻,其中A是主線程,B是子線程初婆;現(xiàn)在我們正在執(zhí)行A線程蓬坡,如果此時(shí)A線程需要用到B線程執(zhí)行完畢時(shí)的一個數(shù)據(jù),此時(shí)我們就可以調(diào)用B線程的jion方法讓線程B執(zhí)行完畢之后再執(zhí)行線程A磅叛;可以理解為插隊(duì)屑咳;
五:守護(hù)線程:
守護(hù)線程的主要作用就是為其他線程服務(wù);例如:計(jì)時(shí)器弊琴,當(dāng)只剩下守護(hù)線程時(shí)虛擬機(jī)就會關(guān)閉自動退出兆龙;守護(hù)線程不會去訪問固定資源:文件,數(shù)據(jù)庫敲董;
通過調(diào)用t.setDaemon(true)將線程t變成守護(hù)線程
六:同步:
-
<1>:基本概念
當(dāng)使用多個線程處理同一組數(shù)據(jù)時(shí)紫皇,就有可能出現(xiàn)兩個線程同時(shí)對這一組數(shù)據(jù)修改的情況;這樣就會造成這一組共享的數(shù)據(jù)出現(xiàn)不合理的情況腋寨;為了防止這一情況發(fā)生我們提出了同步的概念聪铺; - <2>:保持?jǐn)?shù)據(jù)同步的方法
- ** A:使用鎖對象**
java.util.concurrent下面的ReentrantLock來保護(hù)代碼的基本結(jié)構(gòu);
private Lock banklock = new ReentrantLock();
public void transfer(){
MyLock.lock();
Try{
//doSomething();
}Finally{
myLock.unlock();
}
}
上面的結(jié)構(gòu)確保任何時(shí)刻僅僅只能有一個線程進(jìn)入臨界區(qū)萄窜;一旦一個線程封鎖了鎖對象铃剔,其他任何線程都無法通過Lock語句撒桨;當(dāng)其他線程調(diào)用lock時(shí)他們將被阻塞;直到第一個線程釋放鎖對象键兜;PS:釋放鎖的操作一定要放在finally字句中凤类;臨界區(qū)代碼拋出異常,鎖對象必須被釋放蝶押;
鎖是可以重入的踱蠢;線程可以重復(fù)的獲得已經(jīng)持有的鎖;
-
B:條件對象:
只有當(dāng)某個條件滿足了我們才執(zhí)行接下來的操作棋电;此時(shí)我們就需要添加一個條件
private Condition sufficientFunds;
public void transfer(int from,int to,int amount){
banklock.lock();
try{
//不滿足條件就一邊等著去
while(accounts[from]<amount){
sufficientFunds.await();
}
//Transfer fund;
....
suffcientFunds.signAll();
}finally{
banklock.unlock();
}
}
調(diào)用signAll方法并不會立即激活一個線程茎截,他僅僅是讓你從一個對象的等待池中跳出來了;以便這個線程在當(dāng)前線程釋放同步鎖之后赶盔;可以通過競爭實(shí)現(xiàn)對對象的訪問企锌;
總結(jié):
a:鎖用來保護(hù)代碼片段,任何時(shí)刻只能有一個線程執(zhí)行被保護(hù)的代碼
b:鎖可以管理試圖進(jìn)入被保護(hù)代碼段的線程
c:鎖可以擁有一個或者多個條件對象
d:每一個條件對象管理那些已經(jīng)進(jìn)入被保護(hù)的代碼段但還是不能運(yùn)行的線程于未。C:synchronized關(guān)鍵字
java的每一個對象都會有一個內(nèi)部鎖撕攒,如過一段代碼或者一個方法用synchronized關(guān)鍵字聲明,那么對象的鎖將會保護(hù)這個方法烘浦;也就是說想要調(diào)用該方法抖坪,就必須要獲得內(nèi)部的對象鎖;
其實(shí)
public synchronized void method(){
//dosomething;
}
等價(jià)于:
public void method(){
this.intrinsicLock.lock();
try{
//dosomething;
}
}
內(nèi)部對象鎖僅僅只有一個相關(guān)條件闷叉。wait方法添加一個線程到等待集中去擦俐,notify/notifyAll方法解除等待線程的阻塞狀態(tài);等價(jià)于Condition的notify和wait以及notifyAll方法握侧;但是notify和notifyAll以及wait方法都是Object的final方法蚯瞧;由上面的代碼可知synchronized相比ReenrantLock鎖要簡單的多;使用對象鎖最關(guān)鍵的一點(diǎn)是你要理解 每一個對象都有一個內(nèi)部鎖品擎;并且該鎖只有一個內(nèi)部條件埋合;由鎖來管理那些試圖進(jìn)入sychronized方法的線程,由條件來管理那些調(diào)用wait方法的線程萄传;
內(nèi)部鎖和條件存在的一些局限性:
a:不能中斷一個正在試圖獲得鎖的線程
b:試圖獲得鎖時(shí)不能設(shè)定超時(shí)
c:每個鎖僅有單一的條件甚颂,可能是不夠的;
那么在代碼中使用哪一種秀菱?
·1)最好既不使用Lock/Condition也不使用synchronized關(guān)鍵字西设。許多情況我們都可以使用java.util.concurrent包中的一種機(jī)制,它會為你處理所有的代碼加鎖答朋;
2)如果使用synchronized關(guān)鍵字適合你的程序,最好使用它棠笑;減少代碼量梦碗;
3)如果特別需要Lock/Condition結(jié)構(gòu)提供的都具有特性時(shí)才使用Lock/Condition;
-
D:同步阻塞
所謂的同步阻塞其實(shí)就是使用同步代碼代碼塊;
例如:
private double[] accounts;
public void transfer(Vector<Double> accouts,int to,int from){
synchronized(accounts){//截獲這個鎖
acounts.set(from,accounts.get(from)-amount);
acounts.set(to,accounts.get(to)+amount);
}
}
-
E:監(jiān)視器的概念:
雖然鎖和條件是十分強(qiáng)大的同步工具洪规,但是他們不是面對對象的印屁;所以人們提出了監(jiān)視器的概念,它具有以下特征:
a:監(jiān)視器是只包含私有域的類
b:每一個監(jiān)視器類的對象僅僅含有一個相關(guān)的鎖斩例;
c:使用該鎖對所有的方法進(jìn)行加鎖雄人;如果客戶端調(diào)用obj.method();那么obj對象的鎖是在方法調(diào)用開始時(shí)自動獲得的;并且當(dāng)方法返回時(shí)自動釋放該鎖念赶;
d:該鎖可以有任意多個相關(guān)條件
但是Java設(shè)計(jì)者以不是很精確的方式采用了監(jiān)視器的概念础钠;Java的每一個對象都有一個內(nèi)部鎖和內(nèi)部條件;但是他在以下三個方面的不同于監(jiān)視器使其安全性下降:
a:域不要求必須是private
b:方法要求必須是synchronized;
c:內(nèi)部鎖對客戶端是可以使用的 -
F:Volaitile域
目前現(xiàn)狀是:
a:多處理器的計(jì)算機(jī)能夠暫時(shí)在寄存器或本地內(nèi)存緩沖區(qū)中保存內(nèi)存中的值叉谜,這將使得運(yùn)行在不同處理器上的線程可能在同一內(nèi)存位置上取得不同的值旗吁;
b:編譯器可以改變指令的運(yùn)行順序使得指令的吞吐量最大化;雖然這種變化不會改變代碼的語義但是停局,編譯器假定內(nèi)存中的值僅僅只是在代碼有顯示修改指令時(shí)才會改變很钓;然而內(nèi)存的值是可以被其他線程改變的;
有時(shí)僅僅為了的讀寫一兩個域就使用同步顯然會使得程序開銷過大董栽;此時(shí)我們就提出了Volatile概念码倦;通過對一個變量(域)添加Volatile關(guān)鍵字來保證變量(域)安全相對同步來講要容易的多;例如:
private synchronized boolean isDone;
private synchronized void setDone;
private boolean done;
//這種情況下我們就可以將done域聲明為Volatile類型的锭碳;
private boolean isDone;
private void setDone;
private volatile boolean done;
線程寫volatile變量的過程
①改變線程工作內(nèi)存中volatile變量副本的值
②將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存
線程讀volatile變量的過程:
①從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中
②從工作內(nèi)存中讀取volatile變量的副本的值
雖然volatile關(guān)鍵字給我們的省去了同步帶來的不便利性袁稽,但是同時(shí)它也存在一定的安全隱患:
a:volatile是不能保證原子性的例如:done!=done;a++等操作
b:volatile禁止指令重新排序;
總之:以下3種情況下工禾,域的并發(fā)訪問時(shí)安全的:
a:于是final运提,并且在構(gòu)造器調(diào)用完成之后被訪問;
b:對域的訪問使用共有鎖進(jìn)行保護(hù)
c:域是volatile的