導航:首頁 > 編程語言 > 並發編程final屬性

並發編程final屬性

發布時間:2022-11-19 18:57:18

『壹』 java的問題,那幾個變數為什麼要在前面假如final呢

在方法參數前面加final關鍵字就是為了防止數據在方法體中被修改。這里主要分兩種情況:第一,用final修飾基本數據類型;第二,用final修飾引用類型。
第一種情況,修飾基本類型(非引用類型)。這時參數的值在方法體內是不能被修改的,即不能被重新賦值。否則編譯就通不過。例如:
public void testInt(final int param1) {
param1 = 100;
}

在方法體內修改了基本數據類型變數的值,在Eclipse中則會提示:「 The final local variable param1 cannot be assigned. It must be blank and not using a compound assignment.「

第二種情況,修飾引用類型。這時參數變數所引用的對象是不能被改變的。作為引用的拷貝,參數在方法體裡面不能再引用新的對象。否則編譯通不過。例如:
public void testFinal2(final Object param2) {
param2 = new Object();
}
在方法體內修改了參數的引用,在Eclipse中則會提示:「 The final local variable param2 cannot be assigned. It must be blank and not using a compound assignment.「

『貳』 如何創建線程如何保證線程安全

線程安全等級

之前的博客中已有所提及「線程安全」問題,一般我們常說某某類是線程安全的,某某是非線程安全的。其實線程安全並不是一個「非黑即白」單項選擇題。按照「線程安全」的安全程度由強到弱來排序,我們可以將java語言中各種操作共享的數據分為以下5類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立。

1、不可變

在java語言中,不可變的對象一定是線程安全的,無論是對象的方法實現還是方法的調用者,都不需要再採取任何的線程安全保障措施。如final關鍵字修飾的數據不可修改,可靠性最高。

2、絕對線程安全

絕對的線程安全完全滿足Brian GoetZ給出的線程安全的定義,這個定義其實是很嚴格的,一個類要達到「不管運行時環境如何,調用者都不需要任何額外的同步措施」通常需要付出很大的代價。

3、相對線程安全

相對線程安全就是我們通常意義上所講的一個類是「線程安全」的。

它需要保證對這個對象單獨的操作是線程安全的,我們在調用的時候不需要做額外的保障措施,但是對於一些特定順序的連續調用,就可能需要在調用端使用額外的同步手段來保證調用的正確性。

在java語言中,大部分的線程安全類都屬於相對線程安全的,例如Vector、HashTable、Collections的synchronizedCollection()方法保證的集合。

4、線程兼容

線程兼容就是我們通常意義上所講的一個類不是線程安全的。

線程兼容是指對象本身並不是線程安全的,但是可以通過在調用端正確地使用同步手段來保證對象在並發環境下可以安全地使用。Java API中大部分的類都是屬於線程兼容的。如與前面的Vector和HashTable相對應的集合類ArrayList和HashMap等。

5、線程對立

線程對立是指無論調用端是否採取了同步錯誤,都無法在多線程環境中並發使用的代碼。由於java語言天生就具有多線程特性,線程對立這種排斥多線程的代碼是很少出現的。

一個線程對立的例子是Thread類的supend()和resume()方法。如果有兩個線程同時持有一個線程對象,一個嘗試去中斷線程,另一個嘗試去恢復線程,如果並發進行的話,無論調用時是否進行了同步,目標線程都有死鎖風險。正因此如此,這兩個方法已經被廢棄啦。

二、線程安全的實現方法

保證線程安全以是否需要同步手段分類,分為同步方案和無需同步方案。

1、互斥同步

互斥同步是最常見的一種並發正確性保障手段。同步是指在多線程並發訪問共享數據時,保證共享數據在同一時刻只被一個線程使用(同一時刻,只有一個線程在操作共享數據)。而互斥是實現同步的一種手段,臨界區、互斥量和信號量都是主要的互斥實現方式。因此,在這4個字裡面,互斥是因,同步是果;互斥是方法,同步是目的。

在java中,最基本的互斥同步手段就是synchronized關鍵字,synchronized關鍵字編譯之後,會在同步塊的前後分別形成monitorenter和monitorexit這兩個位元組碼質量,這兩個位元組碼指令都需要一個reference類型的參數來指明要鎖定和解鎖的對象。

此外,ReentrantLock也是通過互斥來實現同步。在基本用法上,ReentrantLock與synchronized很相似,他們都具備一樣的線程重入特性。

互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題,因此這種同步也成為阻塞同步。從處理問題的方式上說,互斥同步屬於一種悲觀的並發策略,總是認為只要不去做正確地同步措施(例如加鎖),那就肯定會出現問題,無論共享數據是否真的會出現競爭,它都要進行加鎖。

2、非阻塞同步

隨著硬體指令集的發展,出現了基於沖突檢測的樂觀並發策略,通俗地說,就是先進行操作,如果沒有其他線程爭用共享數據,那操作就成功了;如果共享數據有爭用,產生了沖突,那就再採用其他的補償措施。(最常見的補償錯誤就是不斷地重試,直到成功為止),這種樂觀的並發策略的許多實現都不需要把線程掛起,因此這種同步操作稱為非阻塞同步。

非阻塞的實現CAS(compareandswap):CAS指令需要有3個操作數,分別是內存地址(在java中理解為變數的內存地址,用V表示)、舊的預期值(用A表示)和新值(用B表示)。CAS指令執行時,CAS指令指令時,當且僅當V處的值符合舊預期值A時,處理器用B更新V處的值,否則它就不執行更新,但是無論是否更新了V處的值,都會返回V的舊值,上述的處理過程是一個原子操作。

CAS缺點:

ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。

ABA問題的解決思路就是使用版本號。在變數前面追加版本號,每次變數更新的時候把版本號加一,那麼A-B-A就變成了1A-2B-3C。JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標志是否等於預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。

3、無需同步方案

要保證線程安全,並不是一定就要進行同步,兩者沒有因果關系。同步只是保證共享數據爭用時的正確性的手段,如果一個方法本來就不涉及共享數據,那它自然就無需任何同步操作去保證正確性,因此會有一些代碼天生就是線程安全的。

1)可重入代碼

可重入代碼(ReentrantCode)也稱為純代碼(Pure Code),可以在代碼執行的任何時刻中斷它,轉而去執行另外一段代碼,而在控制權返回後,原來的程序不會出現任何錯誤。所有的可重入代碼都是線程安全的,但是並非所有的線程安全的代碼都是可重入的。

可重入代碼的特點是不依賴存儲在堆上的數據和公用的系統資源、用到的狀態量都是由參數中傳入、不調用 非可重入的方法等。

(類比:synchronized擁有鎖重入的功能,也就是在使用synchronized時,當一個線程得到一個對象鎖後,再次請求此對象鎖時時可以再次得到該對象的鎖)

2)線程本地存儲

如果一段代碼中所需的數據必須與其他代碼共享,那就看看這些共享數據的代碼是否能保證在同一個線程中執行?如果能保證,我們就可以把共享數據的可見范圍限制在同一個線程之內。這樣無需同步也能保證線程之間不出現數據的爭用問題。

符合這種特點的應用並不少見,大部分使用消費隊列的架構模式(如「生產者-消費者」模式)都會將產品的消費過程盡量在一個線程中消費完。其中最重要的一個應用實例就是經典的Web交互模型中的「一個請求對應一個伺服器線程(Thread-per-Request)」的處理方式,這種處理方式的廣泛應用使得很多Web伺服器應用都可以使用線程本地存儲來解決線程安全問題。

『叄』 Java中的關鍵字static和final的作用

final

根據程序上下文環境,Java關鍵字final有「這是無法改變的」或者「終態的」含義,它可以修飾非抽象類、非抽象類成員方法和變數。你可能出於兩種理解而需要阻止改變:設計或效率。

final類不能被繼承,沒有子類,final類中的方法默認是final的。

final方法不能被子類的方法覆蓋,但可以被繼承。

final成員變數表示常量,只能被賦值一次,賦值後值不再改變。

final不能用於修飾構造方法。

注意:父類的private成員方法是不能被子類方法覆蓋的,因此private類型的方法默認是final類型的。

1、final類

final類不能被繼承,因此final類的成員方法沒有機會被覆蓋

,默認都是final的。在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會載被擴展,那麼就設計為final類。

2、final方法

如果一個類不允許其子類覆蓋某個方法,則可以把這個方法聲明為final方法。

使用final方法的原因有二:

第一、把方法鎖定,防止任何繼承類修改它的意義和實現。

第二、高效。編譯器在遇到調用final方法時候會轉入內嵌機制,大大提高執行效率。

例如:

publicclassTest1{
publicstaticvoidmain(String[]args){
//TODO自動生成方法存根
}
publicvoidf1(){
System.out.println("f1");
}
//無法被子類覆蓋的方法
publicfinalvoidf2(){
System.out.println("f2");
}
publicvoidf3(){
System.out.println("f3");
}
privatevoidf4(){
System.out.println("f4");
}
}
publicclassTest2extendsTest1{

publicvoidf1(){
System.out.println("Test1父類方法f1被覆蓋!");
}
publicstaticvoidmain(String[]args){
Test2t=newTest2();
t.f1();
t.f2();//調用從父類繼承過來的final方法
t.f3();//調用從父類繼承過來的方法
//t.f4();//調用失敗,無法從父類繼承獲得
}
}

3、final變數(常量)

用final修飾的成員變數表示常量,值一旦給定就無法改變!

final修飾的變數有三種:靜態變數、實例變數和局部變數,分別表示三種類型的常量。

從下面的例子中可以看出,一旦給final變數初值後,值就不能再改變了。

另外,final變數定義的時候,可以先聲明,而不給初值,這中變數也稱為final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,為此,一個類中的final數據成員就可以實現依對象而有所不同,卻有保持其恆定不變的特徵。

packageorg.leimin;
publicclassTest3{
privatefinalStringS="final實例變數S";
privatefinalintA=100;
publicfinalintB=90;
publicstaticfinalintC=80;
privatestaticfinalintD=70;
publicfinalintE;//final空白,必須在初始化對象的時候賦初值
publicTest3(intx){
E=x;
}
/**
*@paramargs
*/
publicstaticvoidmain(String[]args){
Test3t=newTest3(2);
//t.A=101;//出錯,final變數的值一旦給定就無法改變
//t.B=91;//出錯,final變數的值一旦給定就無法改變
//t.C=81;//出錯,final變數的值一旦給定就無法改變
//t.D=71;//出錯,final變數的值一旦給定就無法改變
System.out.println(t.A);
System.out.println(t.B);
System.out.println(t.C);//不推薦用對象方式訪問靜態欄位
System.out.println(t.D);//不推薦用對象方式訪問靜態欄位
System.out.println(Test3.C);
System.out.println(Test3.D);
//System.out.println(Test3.E);//出錯,因為E為final空白,依據不同對象值有所不同.
System.out.println(t.E);
Test3t1=newTest3(3);
System.out.println(t1.E);//final空白變數E依據對象的不同而不同
}
privatevoidtest(){
System.out.println(newTest3(1).A);
System.out.println(Test3.C);
System.out.println(Test3.D);
}
publicvoidtest2(){
finalinta;//final空白,在需要的時候才賦值
finalintb=4;//局部常量--final用於局部變數的情形
finalintc;//final空白,一直沒有給賦值.
a=3;
//a=4;出錯,已經給賦過值了.
//b=2;出錯,已經給賦過值了.
}
}

4、final參數
當函數參數為final類型時,你可以讀取使用該參數,但是無法改變該參數的值。

publicclassTest4{
publicstaticvoidmain(String[]args){
newTest4().f1(2);
}
publicvoidf1(finalinti){
//i++;//i是final類型的,值不允許改變的.
System.out.print(i);
}
}


二、static


static表示「全局」或者「靜態」的意思,用來修飾成員變數和成員方法,也可以形成靜態static代碼塊,但是Java語言中沒有全局變數的概念。


被static修飾的成員變數和成員方法獨立於該類的任何對象。也就是說,它不依賴類特定的實例,被類的所有實例共享。只要這個類被載入,Java虛擬機就能根據類名在運行時數據區的方法區內定找到他們。因此,static對象可以在它的任何對象創建之前訪問,無需引用任何對象。


用public修飾的static成員變數和成員方法本質是全局變數和全局方法,當聲明它類的對象市,不生成static變數的副本,而是類的所有實例共享同一個static變數。

static變數前可以有private修飾,表示這個變數可以在類的靜態代碼塊中,或者類的其他靜態成員方法中使用(當然也可以在非靜態成員方法中使用--廢話),但是不能在其他類中通過類名來直接引用,這一點很重要。實際上你需要搞明白,private是訪問許可權限定,static表示不要實例化就可以使用,這樣就容易理解多了。static前面加上其它訪問許可權關鍵字的效果也以此類推。

static修飾的成員變數和成員方法習慣上稱為靜態變數和靜態方法,可以直接通過類名來訪問,訪問語法為:

類名.靜態方法名(參數列表...)

類名.靜態變數名

用static修飾的代碼塊表示靜態代碼塊,當Java虛擬機(JVM)載入類時,就會執行該代碼塊(用處非常大,呵呵)。

1、static變數

按照是否靜態的對類成員變數進行分類可分兩種:一種是被static修飾的變數,叫靜態變數或類變數;另一種是沒有被static修飾的變數,叫實例變數。兩者的區別是:

對於靜態變數在內存中只有一個拷貝(節省內存),JVM只為靜態分配一次內存,在載入類的過程中完成靜態變數的內存分配,可用類名直接訪問(方便),當然也可以通過對象來訪問(但是這是不推薦的)。

對於實例變數,沒創建一個實例,就會為實例變數分配一次內存,實例變數可以在內存中有多個拷貝,互不影響(靈活)。

2、靜態方法

靜態方法可以直接通過類名調用,任何的實例也都可以調用,因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的實例變數和實例方法(就是不帶static的成員變數和成員成員方法),只能訪問所屬類的靜態成員變數和成員方法。因為實例成員與特定的對象關聯!這個需要去理解,想明白其中的道理,不是記憶!!!

因為static方法獨立於任何實例,因此static方法必須被實現,而不能是抽象的abstract。

3、static代碼塊

static代碼塊也叫靜態代碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM載入類時會執行這些靜態的代碼塊,如果static代碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個代碼塊只會被執行一次。例如:

publicclassTest5{
privatestaticinta;
privateintb;
static{
Test5.a=3;
System.out.println(a);
Test5t=newTest5();
t.f();
t.b=1000;
System.out.println(t.b);
}
static{
Test5.a=4;
System.out.println(a);
}
publicstaticvoidmain(String[]args){
//TODO自動生成方法存根
}
static{
Test5.a=5;
System.out.println(a);
}
publicvoidf(){
System.out.println("hhahhahah");
}
}

運行結果:
3
hhahhahah
1000
4
5

利用靜態代碼塊可以對一些static變數進行賦值,最後再看一眼這些例子,都一個static的main方法,這樣JVM在運行main方法的時候可以直接調用而不用創建實例。

4、static和final一塊用表示什麼

static final用來修飾成員變數和成員方法,可簡單理解為「全局常量」!

對於變數,表示一旦給值就不可修改,並且通過類名可以訪問。

對於方法,表示不可覆蓋,並且可以通過類名直接訪問。

特別要注意一個問題:

對於被static和final修飾過的實例常量,實例本身不能再改變了,但對於一些容器類型(比如,ArrayList、HashMap)的實例變數,不可以改變容器變數本身,但可以修改容器中存放的對象,這一點在編程中用到很多。

也許說了這么多,反倒把你搞暈了,還是看個例子吧:

publicclassTestStaticFinal{
="aaa";
=null;
privatefinalStringstrFinalVar=null;
=0;
=newInteger(8);
privatestaticfinalArrayList<String>alStaticFinalVar=newArrayList<String>();
privatevoidtest(){
System.out.println("-------------值處理前---------- ");
System.out.println("strStaticFinalVar="+strStaticFinalVar+" ");
System.out.println("strStaticVar="+strStaticVar+" ");
System.out.println("strFinalVar="+strFinalVar+" ");
System.out.println("intStaticFinalVar="+intStaticFinalVar+" ");
System.out.println("integerStaticFinalVar="+integerStaticFinalVar+" ");
System.out.println("alStaticFinalVar="+alStaticFinalVar+" ");
//strStaticFinalVar="哈哈哈哈";//錯誤,final表示終態,不可以改變變數本身.
strStaticVar="哈哈哈哈";//正確,static表示類變數,值可以改變.
//strFinalVar="呵呵呵呵";//錯誤,final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。
//intStaticFinalVar=2;//錯誤,final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。
//integerStaticFinalVar=newInteger(8);//錯誤,final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。
alStaticFinalVar.add("aaa");//正確,容器變數本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。
alStaticFinalVar.add("bbb");//正確,容器變數本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。
System.out.println("-------------值處理後---------- ");
System.out.println("strStaticFinalVar="+strStaticFinalVar+" ");
System.out.println("strStaticVar="+strStaticVar+" ");
System.out.println("strFinalVar="+strFinalVar+" ");
System.out.println("intStaticFinalVar="+intStaticFinalVar+" ");
System.out.println("integerStaticFinalVar="+integerStaticFinalVar+" ");
System.out.println("alStaticFinalVar="+alStaticFinalVar+" ");
}
publicstaticvoidmain(Stringargs[]){
newTestStaticFinal().test();
}
}


看了上面這個例子,就清楚很多了,但必須明白:通過static final修飾的容器類型變數中所「裝」的對象是可改變的。這是和一般基本類型和類類型變數差別很大的地方。

『肆』 並發編程 atomicboolean什麼場景下適用

1.類AtomicBoolean

可以用原子方式更新的 boolean 值。有關原子變數屬性的描述,請參閱 java.util.concurrent.atomic 包規范。AtomicBoolean 可用在應用程序中(如以原子方式更新的標志),但不能用於替換 Boolean。
2.構造函數
1.AtomicBoolean()
使用初始值 false 創建新的 AtomicBoolean。
2.AtomicBoolean(boolean initialValue)
使用給定的初始值創建新的 AtomicBoolean。
3.方法詳解
get
public final boolean get()

返回當前值。

返回:

『伍』 final的作用

一、final關鍵字的基本用法

在Java中,final關鍵字可以用來修飾類、方法和變數(包括成員變數和局部變數)。下面就從這三個方面來了解一下final關鍵字的基本用法。

1、修飾類

當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變數可以根據需要設為final,但是要注意final類中的所有成員方法都會被隱式地指定為final方法。

在使用final修飾類的時候,要注意謹慎選擇,除非這個類真的在以後不會用來繼承或者出於安全的考慮,盡量不要將類設計為final類。

2/6
2、修飾方法

下面這段話摘自《Java編程思想》第四版第143頁:

「使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉為內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。「

因此,如果只有在想明確禁止 該方法在子類中被覆蓋的情況下才將方法設置為final的。即父類的final方法是不能被子類所覆蓋的,也就是說子類是不能夠存在和父類一模一樣的方法的。

final修飾的方法表示此方法已經是「最後的、最終的」含義,亦即此方法不能被重寫(可以重載多個final修飾的方法)。此處需要注意的一點是:因為重寫的前提是子類可以從父類中繼承此方法,如果父類中final修飾的方法同時訪問控制許可權為private,將會導致子類中不能直接繼承到此方法,因此,此時可以在子類中定義相同的方法名和參數,此時不再產生重寫與final的矛盾,而是在子類中重新定義了新的方法。(註:類的private方法會隱式地被指定為final方法。)

3/6
3、修飾變數 修飾變數是final用得最多的地方,也是本文接下來要重點闡述的內容。

final成員變數表示常量,只能被賦值一次,賦值後值不再改變。

當final修飾一個基本數據類型時,表示該基本數據類型的值一旦在初始化後便不能發生變化;如果final修飾一個引用類型時,則在對其初始化之後便不能再讓其指向其他對象了,但該引用所指向的對象的內容是可以發生變化的。本質上是一回事,因為引用的值是一個地址,final要求值,即地址的值不發生變化。

final修飾一個成員變數(屬性),必須要顯示初始化。這里有兩種初始化方式,一種是在變數聲明的時候初始化;第二種方法是在聲明變數的時候不賦初值,但是要在這個變數所在的類的所有的構造函數中對這個變數賦初值。

當函數的參數類型聲明為final時,說明該參數是只讀型的。即你可以讀取使用該參數,但是無法改變該參數的值。

4/6
二、深入理解final關鍵字

在了解了final關鍵字的基本用法之後,這一節我們來看一下final關鍵字容易混淆的地方。

1、類的final變數和普通變數有什麼區別?

當用final作用於類的成員變數時,成員變數(注意是類的成員變數,局部變數只需要保證在使用之前被初始化賦值即可)必須在定義時或者構造器中進行初始化賦值,而且final變數一旦被初始化賦值之後,就不能再被賦值了。

5/6
2、被final修飾的引用變數指向的對象內容可變嗎?

引用變數被final修飾之後,雖然不能再指向其他對象,但是它指向的對象的內容是可變的

6/6
3、final參數的問題

在實際應用中,我們除了可以用final修飾成員變數、成員方法、類,還可以修飾參數、若某個參數被final修飾了,則代表了該參數是不可改變的。如果在方法中我們修改了該參數,則編譯器會提示你:The final local variable i cannot be assigned. It must be blank and not using a compound assignment。

java採用的是值傳遞,對於引用變數,傳遞的是引用的值,也就是說讓實參和形參同時指向了同一個對象,因此讓形參重新指向另一個對象對實參並沒有任何影響。

『陸』 在方法參數中使用final 有什麼用

1 常量(final)的設置是不是只是為了防止程序員在以後修改程序時,忘記這個值是不能修改的?如果假設「程序員一定記得」這個值不允許改變,那麼就可以不設為final吧?

程序員工作不是一個人的工作,你設置為final,別人將來維護的時候一看就知道這個變數不能修改,而不需要去記憶這個是不能變化的值,是常量。這個是代碼規范。
另外final常量一般設置為static的,一定程度上節省了內存開銷。

2 有些方法的參數中會聲明參數為final,這樣用有什麼用? 例如 public A(final Map map);
參數聲明為final就是標明這個對象的內存地址不允許修改。即就是如果你在方法裡面修改為
map=newHashMap();是不允許的。但是map裡面的值可以修改。
舉一個例子
public class Test {

public static void main(String[] args) {

Map map = new HashMap();

process(map);

System.out.println(map.keySet().iterator().next());//結果輸出aaa
}

private static void process(final Map map) {
map.put("aaaa", "1111");//盡管map為final,但是只是地址不變
//map裡面的內容可以改變
//map = new HashMap();//當從新為final變數map分配內存地址,
//引起編譯錯誤,因為final修飾的map內存地址不可以改變。

}

}

『柒』 並發編程解惑之線程

主要內容:

進程是資源分配的最小單位,每個進程都有獨立的代碼和數據空間,一個進程包含 1 到 n 個線程。線程是 CPU 調度的最小單位,每個線程有獨立的運行棧和程序計數器,線程切換開銷小。

Java 程序總是從主類的 main 方法開始執行,main 方法就是 Java 程序默認的主線程,而在 main 方法中再創建的線程就是其他線程。在 Java 中,每次程序啟動至少啟動 2 個線程。一個是 main 線程,一個是垃圾收集線程。每次使用 Java 命令啟動一個 Java 程序,就相當於啟動一個 JVM 實例,而每個 JVM 實例就是在操作系統中啟動的一個進程。

多線程可以通過繼承或實現介面的方式創建。

Thread 類是 JDK 中定義的用於控制線程對象的類,該類中封裝了線程執行體 run() 方法。需要強調的一點是,線程執行先後與創建順序無關。

通過 Runnable 方式創建線程相比通過繼承 Thread 類創建線程的優勢是避免了單繼承的局限性。若一個 boy 類繼承了 person 類,boy 類就無法通過繼承 Thread 類的方式來實現多線程。

使用 Runnable 介面創建線程的過程:先是創建對象實例 MyRunnable,然後將對象 My Runnable 作為 Thread 構造方法的入參,來構造出線程。對於 new Thread(Runnable target) 創建的使用同一入參目標對象的線程,可以共享該入參目標對象 MyRunnable 的成員變數和方法,但 run() 方法中的局部變數相互獨立,互不幹擾。

上面代碼是 new 了三個不同的 My Runnable 對象,如果只想使用同一個對象,可以只 new 一個 MyRunnable 對象給三個 new Thread 使用。

實現 Runnable 介面比繼承 Thread 類所具有的優勢:

線程有新建、可運行、阻塞、等待、定時等待、死亡 6 種狀態。一個具有生命的線程,總是處於這 6 種狀態之一。 每個線程可以獨立於其他線程運行,也可和其他線程協同運行。線程被創建後,調用 start() 方法啟動線程,該線程便從新建態進入就緒狀態。

NEW 狀態(新建狀態) 實例化一個線程之後,並且這個線程沒有開始執行,這個時候的狀態就是 NEW 狀態:

RUNNABLE 狀態(就緒狀態):

阻塞狀態有 3 種:

如果一個線程調用了一個對象的 wait 方法, 那麼這個線程就會處於等待狀態(waiting 狀態)直到另外一個線程調用這個對象的 notify 或者 notifyAll 方法後才會解除這個狀態。

run() 里的代碼執行完畢後,線程進入終結狀態(TERMINATED 狀態)。

線程狀態有 6 種:新建、可運行、阻塞、等待、定時等待、死亡。

我們看下 join 方法的使用:

運行結果:

我們來看下 yield 方法的使用:

運行結果:

線程與線程之間是無法直接通信的,A 線程無法直接通知 B 線程,Java 中線程之間交換信息是通過共享的內存來實現的,控制共享資源的讀寫的訪問,使得多個線程輪流執行對共享數據的操作,線程之間通信是通過對共享資源上鎖或釋放鎖來實現的。線程排隊輪流執行共享資源,這稱為線程的同步。

Java 提供了很多同步操作(也就是線程間的通信方式),同步可使用 synchronized 關鍵字、Object 類的 wait/notifyAll 方法、ReentrantLock 鎖、無鎖同步 CAS 等方式來實現。

ReentrantLock 是 JDK 內置的一個鎖對象,用於線程同步(線程通信),需要用戶手動釋放鎖。

運行結果:

這表明同一時間段只能有 1 個線程執行 work 方法,因為 work 方法里的代碼需要獲取到鎖才能執行,這就實現了多個線程間的通信,線程 0 獲取鎖,先執行,線程 1 等待,線程 0 釋放鎖,線程 1 繼續執行。

synchronized 是一種語法級別的同步方式,稱為內置鎖。該鎖會在代碼執行完畢後由 JVM 釋放。

輸出結果跟 ReentrantLock 一樣。

Java 中的 Object 類默認是所有類的父類,該類擁有 wait、 notify、notifyAll 方法,其他對象會自動繼承 Object 類,可調用 Object 類的這些方法實現線程間的通信。

除了可以通過鎖的方式來實現通信,還可通過無鎖的方式來實現,無鎖同 CAS(Compare-and-Swap,比較和交換)的實現,需要有 3 個操作數:內存地址 V,舊的預期值 A,即將要更新的目標值 B,當且僅當內存地址 V 的值與預期值 A 相等時,將內存地址 V 的值修改為目標值 B,否則就什麼都不做。

我們通過計算器的案例來演示無鎖同步 CAS 的實現方式,非線程安全的計數方式如下:

線程安全的計數方式如下:

運行結果:

線程安全累加的結果才是正確的,非線程安全會出現少計算值的情況。JDK 1.5 開始,並發包里提供了原子操作的類,AtomicBoolean 用原子方式更新的 boolean 值,AtomicInteger 用原子方式更新 int 值,AtomicLong 用原子方式更新 long 值。 AtomicInteger 和 AtomicLong 還提供了用原子方式將當前值自增 1 或自減 1 的方法,在多線程程序中,諸如 ++i 或 i++ 等運算不具有原子性,是不安全的線程操作之一。 通常我們使用 synchronized 將該操作變成一個原子操作,但 JVM 為此種操作提供了原子操作的同步類 Atomic,使用 AtomicInteger 做自增運算的性能是 ReentantLock 的好幾倍。

上面我們都是使用底層的方式實現線程間的通信的,但在實際的開發中,我們應該盡量遠離底層結構,使用封裝好的 API,例如 J.U.C 包(java.util.concurrent,又稱並發包)下的工具類 CountDownLath、CyclicBarrier、Semaphore,來實現線程通信,協調線程執行。

CountDownLatch 能夠實現線程之間的等待,CountDownLatch 用於某一個線程等待若干個其他線程執行完任務之後,它才開始執行。

CountDownLatch 類只提供了一個構造器:

CountDownLatch 類中常用的 3 個方法:

運行結果:

CyclicBarrier 字面意思循環柵欄,通過它可以讓一組線程等待至某個狀態之後再全部同時執行。當所有等待線程都被釋放以後,CyclicBarrier 可以被重復使用,所以有循環之意。

相比 CountDownLatch,CyclicBarrier 可以被循環使用,而且如果遇到線程中斷等情況時,可以利用 reset() 方法,重置計數器,CyclicBarrier 會比 CountDownLatch 更加靈活。

CyclicBarrier 提供 2 個構造器:

上面的方法中,參數 parties 指讓多少個線程或者任務等待至 barrier 狀態;參數 barrierAction 為當這些線程都達到 barrier 狀態時會執行的內容。

CyclicBarrier 中最重要的方法 await 方法,它有 2 個重載版本。下面方法用來掛起當前線程,直至所有線程都到達 barrier 狀態再同時執行後續任務。

而下面的方法則是讓這些線程等待至一定的時間,如果還有線程沒有到達 barrier 狀態就直接讓到達 barrier 的線程執行任務。

運行結果:

CyclicBarrier 用於一組線程互相等待至某個狀態,然後這一組線程再同時執行,CountDownLatch 是不能重用的,而 CyclicBarrier 可以重用。

Semaphore 類是一個計數信號量,它可以設定一個閾值,多個線程競爭獲取許可信號,執行完任務後歸還,超過閾值後,線程申請許可信號時將會被阻塞。Semaphore 可以用來 構建對象池,資源池,比如資料庫連接池。

假如在伺服器上運行著若干個客戶端請求的線程。這些線程需要連接到同一資料庫,但任一時刻只能獲得一定數目的資料庫連接。要怎樣才能夠有效地將這些固定數目的資料庫連接分配給大量的線程呢?

給方法加同步鎖,保證同一時刻只能有一個線程去調用此方法,其他所有線程排隊等待,但若有 10 個資料庫連接,也只有一個能被使用,效率太低。另外一種方法,使用信號量,讓信號量許可與資料庫可用連接數為相同數量,10 個資料庫連接都能被使用,大大提高性能。

上面三個工具類是 J.U.C 包的核心類,J.U.C 包的全景圖就比較復雜了:

J.U.C 包(java.util.concurrent)中的高層類(Lock、同步器、阻塞隊列、Executor、並發容器)依賴基礎類(AQS、非阻塞數據結構、原子變數類),而基礎類是通過 CAS 和 volatile 來實現的。我們盡量使用頂層的類,避免使用基礎類 CAS 和 volatile 來協調線程的執行。J.U.C 包其他的內容,在其他的篇章會有相應的講解。

Future 是一種非同步執行的設計模式,類似 ajax 非同步請求,不需要同步等待返回結果,可繼續執行代碼。使 Runnable(無返回值不支持上報異常)或 Callable(有返回值支持上報異常)均可開啟線程執行任務。但是如果需要非同步獲取線程的返回結果,就需要通過 Future 來實現了。

Future 是位於 java.util.concurrent 包下的一個介面,Future 介面封裝了取消任務,獲取任務結果的方法。

在 Java 中,一般是通過繼承 Thread 類或者實現 Runnable 介面來創建多線程, Runnable 介面不能返回結果,JDK 1.5 之後,Java 提供了 Callable 介面來封裝子任務,Callable 介面可以獲取返回結果。我們使用線程池提交 Callable 介面任務,將返回 Future 介面添加進 ArrayList 數組,最後遍歷 FutureList,實現非同步獲取返回值。

運行結果:

上面就是非同步線程執行的調用過程,實際開發中用得更多的是使用現成的非同步框架來實現非同步編程,如 RxJava,有興趣的可以繼續去了解,通常非同步框架都是結合遠程 HTTP 調用 Retrofit 框架來使用的,兩者結合起來用,可以避免調用遠程介面時,花費過多的時間在等待介面返回上。

線程封閉是通過本地線程 ThreadLocal 來實現的,ThreadLocal 是線程局部變數(local vari able),它為每個線程都提供一個變數值的副本,每個線程對該變數副本的修改相互不影響。

在 JVM 虛擬機中,堆內存用於存儲共享的數據(實例對象),也就是主內存。Thread Local .set()、ThreadLocal.get() 方法直接在本地內存(工作內存)中寫和讀共享變數的副本,而不需要同步數據,不用像 synchronized 那樣保證數據可見性,修改主內存數據後還要同步更新到工作內存。

Myabatis、hibernate 是通過 threadlocal 來存儲 session 的,每一個線程都維護著一個 session,對線程獨享的資源操作很方便,也避免了線程阻塞。

ThreadLocal 類位於 Thread 線程類內部,我們分析下它的源碼

ThreadLocal 和 Synchonized 都用於解決多線程並發訪問的問題,訪問多線程共享的資源時,Synchronized 同步機制採用了以時間換空間的方式,提供一份變數讓多個線程排隊訪問,而 ThreadLocal 採用了以空間換時間的方式,提供每個線程一個變數,實現數據隔離。

ThreadLocal 可用於資料庫連接 Connection 對象的隔離,使得每個請求線程都可以復用連接而又相互不影響。

在 Java 裡面,存在強引用、弱引用、軟引用、虛引用。我們主要來了解下強引用和弱引用:

上面 a、b 對實例 A、B 都是強引用

而上面這種情況就不一樣了,即使 b 被置為 null,但是 c 仍然持有對 C 對象實例的引用,而間接的保持著對 b 的強引用,所以 GC 不會回收分配給 b 的空間,導致 b 無法回收也沒有被使用,造成了內存泄漏。這時可以通過 c = null; 來使得 c 被回收,但也可以通過弱引用來達到同樣目的:

從源碼中可以看出 Entry 里的 key 對 ThreadLocal 實例是弱引用:

Entry 里的 key 對 ThreadLocal 實例是弱引用,將 key 值置為 null,堆中的 ThreadLocal 實例是可以被垃圾收集器(GC)回收的。但是 value 卻存在一條從 Current Thread 過來的強引用鏈,只有當當前線程 Current Thread 銷毀時,value 才能被回收。在 threadLocal 被設為 null 以及線程結束之前,Entry 的鍵值對都不會被回收,出現內存泄漏。為了避免泄漏,在 ThreadLocalMap 中的 set/get Entry 方法里,會對 key 為 null 的情況進行判斷,如果為 null 的話,就會對 value 置為 null。也可以通過 ThreadLocal 的 remove 方法(類似加鎖和解鎖,最後 remove 一下,解鎖對象的引用)直接清除,釋放內存空間。

總結來說,利用 ThreadLocal 來訪問共享數據時,JVM 通過設置 ThreadLocalMap 的 Key 為弱引用,來避免內存泄露,同時通過調用 remove、get、set 方法的時候,回收弱引用(Key 為 null 的 Entry)。當使用 static ThreadLocal 的時候(如上面的 Spring 多數據源),static 變數在類未載入的時候,它就已經載入,當線程結束的時候,static 變數不一定會被回收,比起普通成員變數使用的時候才載入,static 的生命周期變長了,若沒有及時回收,容易產生內存泄漏。

使用線程池,可以重用存在的線程,減少對象創建、消亡的開銷,可控制最大並發線程數,避免資源競爭過度,還能實現線程定時執行、單線程執行、固定線程數執行等功能。

Java 把線程的調用封裝成了一個 Executor 介面,Executor 介面中定義了一個 execute 方法,用來提交線程的執行。Executor 介面的子介面是 ExecutorService,負責管理線程的執行。通過 Executors 類的靜態方法可以初始化

ExecutorService 線程池。Executors 類的靜態方法可創建不同類型的線程池:

但是,不建議使用 Executors 去創建線程池,而是通過 ThreadPoolExecutor 的方式,明確給出線程池的參數去創建,規避資源耗盡的風險。

如果使用 Executors 去創建線程池:

最佳的實踐是通過 ThreadPoolExecutor 手動地去創建線程池,選取合適的隊列存儲任務,並指定線程池線程大小。通過線程池實現類 ThreadPoolExecutor 可構造出線程池的,構造函數有下面幾個重要的參數:

參數 1:corePoolSize

線程池核心線程數。

參數 2:workQueue

阻塞隊列,用於保存執行任務的線程,有 4 種阻塞隊列可選:

參數 3:maximunPoolSize

線程池最大線程數。如果阻塞隊列滿了(有界的阻塞隊列),來了一個新的任務,若線程池當前線程數小於最大線程數,則創建新的線程執行任務,否則交給飽和策略處理。如果是無界隊列就不存在這種情況,任務都在無界隊列里存儲著。

參數 4:RejectedExecutionHandler

拒絕策略,當隊列滿了,而且線程達到了最大線程數後,對新任務採取的處理策略。

有 4 種策略可選:

最後,還可以自定義處理策略。

參數 5:ThreadFactory

創建線程的工廠。

參數 6:keeyAliveTime

線程沒有任務執行時最多保持多久時間終止。當線程池中的線程數大於 corePoolSize 時,線程池中所有線程中的某一個線程的空閑時間若達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。但如果調用了 allowCoreThread TimeOut(boolean value) 方法,線程池中的線程數就算不超過 corePoolSize,keepAlive Time 參數也會起作用,直到線程池中的線程數量變為 0。

參數 7:TimeUnit

配合第 6 個參數使用,表示存活時間的時間單位最佳的實踐是通過 ThreadPoolExecutor 手動地去創建線程池,選取合適的隊列存儲任務,並指定線程池線程大小。

運行結果:

線程池創建線程時,會將線程封裝成工作線程 Worker,Worker 在執行完任務後,還會不斷的去獲取隊列里的任務來執行。Worker 的加鎖解鎖機制是繼承 AQS 實現的。

我們來看下 Worker 線程的運行過程:

總結來說,如果當前運行的線程數小於 corePoolSize 線程數,則獲取全局鎖,然後創建新的線程來執行任務如果運行的線程數大於等於 corePoolSize 線程數,則將任務加入阻塞隊列 BlockingQueue 如果阻塞隊列已滿,無法將任務加入 BlockingQueue,則獲取全局所,再創建新的線程來執行任務

如果新創建線程後使得線程數超過了 maximumPoolSize 線程數,則調用 Rejected ExecutionHandler.rejectedExecution() 方法根據對應的拒絕策略處理任務。

CPU 密集型任務,線程執行任務佔用 CPU 時間會比較長,應該配置相對少的線程數,避免過度爭搶資源,可配置 N 個 CPU+1 個線程的線程池;但 IO 密集型任務則由於需要等待 IO 操作,線程經常處於等待狀態,應該配置相對多的線程如 2*N 個 CPU 個線程,A 線程阻塞後,B 線程能馬上執行,線程多競爭激烈,能飽和的執行任務。線程提交 SQL 後等待資料庫返回結果時間較長的情況,CPU 空閑會較多,線程數應設置大些,讓更多線程爭取 CPU 的調度。

『捌』 android 編程中 final是什麼意思

final 如果用在類中就是最終類,不能被繼承;若用在變數中,就相當於是一個常量;用在函數中,這個方法不能被重寫;就是這個意思

『玖』 java語言中 final和static 的 區別是什麼

final 被修飾的變數為常量一旦賦值不能修改,被修改的方法為最終方法不能

被重寫,被修飾的類是最終類,不能被繼承

static 被修飾的變數和方法,為該整個類及其類的對象所共享,一個類或對

象修改了被定義的類或方法,其他的也會跟著變數。。這就是說被static修飾的

是大家共用的。。

就一塊肥皂多個人用,每個人用都會少而不是像不用static修飾的是每個人有

每個人的肥皂,你用自己的和別人沒有關系。。

祝你好運!

如果還是不很明白,到我空間轉轉也許會更明白。。。

閱讀全文

與並發編程final屬性相關的資料

熱點內容
有什麼學習高中語文的app 瀏覽:280
安卓手機的表格里怎麼打勾 瀏覽:407
阿里雲伺服器有網路安全服務嗎 瀏覽:966
超解壓兔子視頻 瀏覽:22
單片機怎麼測負脈沖 瀏覽:172
魅族備份的app在哪裡 瀏覽:738
java倒三角列印 瀏覽:112
通達信回封板主圖源碼 瀏覽:44
戰地什麼伺服器 瀏覽:299
安卓為什麼老是閃退怎麼辦 瀏覽:803
樂高機器人的編程軟體下載 瀏覽:223
工作中怎麼使用加密狗 瀏覽:735
雲伺服器的後台找不到 瀏覽:98
php逐行寫入文件 瀏覽:912
javaoracleweb 瀏覽:440
京東加密碼怎麼弄 瀏覽:467
單片機程序員培訓 瀏覽:992
PHP商城源代碼csdn 瀏覽:636
怎麼把電腦里文件夾挪出來 瀏覽:693
java流程處理 瀏覽:685