?????在Java中腌巾,final關(guān)鍵字可以用來(lái)修飾類(lèi)杠袱、方法和變量(類(lèi)變量和實(shí)例變量以及局部變量)昵济,在Java中做到了無(wú)孔不入智绸,這些都是在語(yǔ)法層面的限制,在編譯期就會(huì)限制住访忿。其作用如下:
- final修飾的類(lèi):不能被繼承
- final修飾的方法:不能被重寫(xiě)瞧栗,但是可以重載
- final修飾的變量:不能被修改,如果修飾的是引用的時(shí)候海铆,引用所指向的地址值不能修改迹恐,但是引用所指的對(duì)象內(nèi)部變量值可以被修改。
?????final內(nèi)存語(yǔ)義:但是final在多線程中的內(nèi)存語(yǔ)義很多時(shí)候都會(huì)被忽略了卧斟,在Java中為了提高程序的運(yùn)行效率殴边,編譯器和處理器都有可能會(huì)重排序。final在多線程中的內(nèi)存語(yǔ)義如下:
- 在構(gòu)造函數(shù)內(nèi)對(duì)final域數(shù)據(jù)的寫(xiě)入珍语,與隨后把這個(gè)被構(gòu)造的對(duì)象賦值一個(gè)引用之間锤岸,這兩個(gè)操作之間是不允許重排序的。
- 在讀一個(gè)對(duì)象引用與隨后讀對(duì)象final域數(shù)據(jù)之間板乙,這兩個(gè)操作之間是不允許重排序的是偷。
public class FinalTest {
private int i;
private final int value;
static FinalTest object;
public FinalTest() {
i = 1;
value = 2;
}
public static void write() {
object = new FinalTest();
}
public static void read() {
FinalTest obj = object;//@1
System.out.println(obj.i);
System.out.println(obj.value);//@2
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> FinalTest.write());
Thread t2 = new Thread(() -> FinalTest.read());
t1.start();
t2.start();
Thread.sleep(10000);
}
}
?????以上面的例子來(lái)說(shuō):
- 對(duì)final寫(xiě)內(nèi)存語(yǔ)義來(lái)說(shuō),F(xiàn)inalTest構(gòu)造函數(shù)返回之前亡驰,final修飾的變量value已經(jīng)被賦值了晓猛,value = 2操作在構(gòu)造函數(shù)返回之前就已經(jīng)完成賦值。實(shí)現(xiàn)方式是有2步:禁止編譯器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之后凡辱,并且在final域?qū)懼蠛蜆?gòu)造函數(shù)返回之前的地方插入一個(gè)storestore內(nèi)存屏障戒职,這樣就能禁止處理器在執(zhí)行的時(shí)候重排序。
- 對(duì)final讀內(nèi)存語(yǔ)義來(lái)說(shuō)透乾,讀對(duì)應(yīng)引用操作和讀對(duì)象final域數(shù)據(jù)操作洪燥,兩者之間不能重排序磕秤。@1標(biāo)記的操作總是在@2標(biāo)記處的操作之前執(zhí)行。實(shí)現(xiàn)方式也是2步:禁止編譯器把讀對(duì)象引用和讀對(duì)象final域數(shù)據(jù)的兩步操作進(jìn)行重排序捧韵,并且在讀final域操作的前面插入loadload內(nèi)存屏障市咆。
?????final域的重排序的作用:在引用變量為任意線程可見(jiàn)之前,引用變量指向的對(duì)象的final域已經(jīng)在構(gòu)造函數(shù)中被正確初始化了再来。這里有個(gè)前提就是:該對(duì)象是被正確構(gòu)造的蒙兰,在構(gòu)造函數(shù)中沒(méi)有發(fā)生逸出。
public class FinalOut {
final int value;
static FinalOut object;
public FinalOut() {
value = 1;
object = this;
}
public static void write() {
new FinalOut();
}
public static void read() {
if (object != null) {
System.out.println(object.value);
}
}
}
?????這里的構(gòu)造函數(shù)中芒篷,就發(fā)生了this引用的逸出搜变,這樣的對(duì)象是沒(méi)有被正確構(gòu)造的,所以在這里final的內(nèi)存語(yǔ)義是沒(méi)有辦法保證的针炉。簡(jiǎn)單來(lái)說(shuō)挠他,只要對(duì)象被正確構(gòu)造,沒(méi)有發(fā)生對(duì)象this引用的逸出問(wèn)題篡帕,那么不需要使用同步殖侵,就可以保證任意線程都能看到這個(gè)對(duì)象的final域在構(gòu)造函數(shù)中被初始化的值。