成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Java面試題,深入理解final關(guān)鍵字

番茄西紅柿 / 3079人閱讀

摘要:寫域重排序規(guī)則寫域的重排序規(guī)則禁止對(duì)域的寫重排序到構(gòu)造函數(shù)之外,這個(gè)規(guī)則的實(shí)現(xiàn)主要包含了兩個(gè)方面禁止編譯器把域的寫重排序到構(gòu)造函數(shù)之外編譯器會(huì)在域?qū)懼?,?gòu)造函數(shù)之前,插入一個(gè)屏障。結(jié)論只有當(dāng)構(gòu)造函數(shù)返回時(shí),引用才應(yīng)該從線程中逸出。

final關(guān)鍵字final的簡(jiǎn)介

final可以修飾變量,方法和類,用于表示所修飾的內(nèi)容一旦賦值之后就不會(huì)再被改變,比如String類就是一個(gè)final類型的類。

final的具體使用場(chǎng)景

final能夠修飾變量,方法和類,也就是final使用范圍基本涵蓋了java每個(gè)地方, 下面就分別以鎖修飾的位置:變量,方法和類分別介紹。

final修飾成員變量
public class FinalExample {    //聲明變量的時(shí)候,就進(jìn)行初始化
    private final int num=6;    //類變量必須要在靜態(tài)初始化塊中指定初始值或者聲明該類變量時(shí)指定初始值
    // private final String str; //編譯錯(cuò)誤:因?yàn)榉庆o態(tài)變量不可以在靜態(tài)初始化快中賦初值
    private final static String name;    private final double score;    private final char ch;    //private final char ch2;//編譯錯(cuò)誤:TODO:因?yàn)闆](méi)有在構(gòu)造器、初始化代碼塊和聲明時(shí)賦值
    
    {        //實(shí)例變量在初始化代碼塊賦初值
        ch=a;
    }    
    static {
        name="aaaaa";
    }    
    public FinalExample(){        //num=1;編譯錯(cuò)誤:已經(jīng)賦值后,就不能再修改了
        score=90.0;
    }    
    public void ch2(){        //ch2=c;//編譯錯(cuò)誤:實(shí)例方法無(wú)法給final變量賦值
    }
}

類變量:必須要在靜態(tài)初始化塊中指定初始值或者聲明該類變量時(shí)指定初始值,而且只能在這兩個(gè)地方之一進(jìn)行指定

實(shí)例變量:必要要在非靜態(tài)初始化塊,聲明該實(shí)例變量或者在構(gòu)造器中指定初始值,而且只能在這三個(gè)地方進(jìn)行指定

final修飾局部變量

final局部變量由程序員進(jìn)行顯式初始化, 如果final局部變量已經(jīng)進(jìn)行了初始化則后面就不能再次進(jìn)行更改, 如果final變量未進(jìn)行初始化,可以進(jìn)行賦值,當(dāng)且僅有一次賦值,一旦賦值之后再次賦值就會(huì)出錯(cuò)。

public void test(){    final int a=1;    //a=2;//編譯錯(cuò)誤:final局部變量已經(jīng)進(jìn)行了初始化則后面就不能再次進(jìn)行更改}

final基本數(shù)據(jù)類型 VS final引用數(shù)據(jù)類型

如果final修飾的是一個(gè)基本數(shù)據(jù)類型的數(shù)據(jù),一旦賦值后就不能再次更改, 那么,如果final是引用數(shù)據(jù)類型了?這個(gè)引用的對(duì)象能夠改變嗎?

public class FinalExample2 {    private static class Person {        private String name;        private int age;        public void setName(String name) {            this.name = name;
        }        public String getName() {            return name;
        }        public void setAge(int age) {            this.age = age;
        }        public int getAge() {            return age;
        }        public Person(String name, int age) {            this.name=name;            this.age = age;
        }        @Override
        public String toString() {            StringBuilder res=new StringBuilder();
            res.append("[").append("name="+name+",age="+age).append("]");            return res.toString();
        }
    }    private static final Person person=new Person("小李子",23);    public static void main(String[] args) {        System.out.println(person);
        person.setAge(24);        System.out.println(person);
    }
}

輸出結(jié)果:

[name=小李子,age=23]
[name=小李子,age=24]

當(dāng)我們對(duì)final修飾的引用數(shù)據(jù)類型變量person的屬性改成24,是可以成功操作的。 通過(guò)這個(gè)實(shí)驗(yàn)我們就可以看出來(lái)當(dāng)final修飾基本數(shù)據(jù)類型變量時(shí),不能對(duì)基本數(shù)據(jù)類型變量重新賦值, 因此基本數(shù)據(jù)類型變量不能被改變。 而對(duì)于引用類型變量而言,它僅僅保存的是一個(gè)引用,final只保證這個(gè)引用類型變量所引用的地址不會(huì)發(fā)生改變, 即一直引用這個(gè)對(duì)象,但這個(gè)對(duì)象屬性是可以改變的。

宏變量

利用final變量的不可更改性,在滿足以下三個(gè)條件時(shí),該變量就會(huì)成為一個(gè)“宏變量”,即是一個(gè)常量。

使用final修飾符修飾

在定義該final變量時(shí)就指定了初始值;

該初始值在編譯時(shí)就能夠唯一確定

注意:當(dāng)程序中其他地方使用該宏變量的地方,編譯器會(huì)直接替換成該變量的值。

final修飾方法

重寫(Override)

被final修飾的方法不能夠被子類所重寫。 比如在Object中,getClass()方法就是final的,我們就不能重寫該方法, 但是hashCode()方法就不是被final所修飾的,我們就可以重寫hashCode()方法。

重載(Overload)

被final修飾的方法是可以重載的

final修飾類

當(dāng)一個(gè)類被final修飾時(shí),該類是不能被子類繼承的。 子類繼承往往可以重寫父類的方法和改變父類屬性,會(huì)帶來(lái)一定的安全隱患, 因此,當(dāng)一個(gè)類不希望被繼承時(shí)就可以使用final修飾。

不可變類

final經(jīng)常會(huì)被用作不變類上。我們先來(lái)看看什么是不可變類:

使用private和final修飾符來(lái)修飾該類的成員變量

提供帶參的構(gòu)造器用于初始化類的成員變量

僅為該類的成員變量提供getter方法,不提供setter方法,因?yàn)槠胀ǚ椒o(wú)法修改fina修飾的成員變量

如果有必要就重寫Object類的hashCode()和equals()方法,應(yīng)該保證用equals()判斷相同的兩個(gè)對(duì)象其Hashcode值也是相等的。

JDK中提供的八個(gè)包裝類和String類都是不可變類。

final域重排序規(guī)則 final為基本類型
public class FinalDemo {    private int a;  //普通域
    private final int b; //final域-->int基本類型
    private static FinalDemo finalDemo;//引用類型,但不是final修飾的

    public FinalDemo() {
        a = 1; // 1. 寫普通域
        b = 2; // 2. 寫final域
    }    public static void writer() {
        finalDemo = new FinalDemo();
    }    public static void reader() {        FinalDemo demo = finalDemo; // 3.讀對(duì)象引用
        int a = demo.a;    //4.讀普通域
        int b = demo.b;    //5.讀final域
    }
}

假設(shè)線程A在執(zhí)行writer()方法,線程B執(zhí)行reader()方法。

寫final域重排序規(guī)則

寫final域的重排序規(guī)則:禁止對(duì)final域的寫重排序到構(gòu)造函數(shù)之外,這個(gè)規(guī)則的實(shí)現(xiàn)主要包含了兩個(gè)方面:

JMM禁止編譯器把final域的寫重排序到構(gòu)造函數(shù)之外

編譯器會(huì)在final域?qū)懼?,?gòu)造函數(shù)return之前,插入一個(gè)StoreStore屏障。 這個(gè)屏障可以禁止處理器把final域的寫重排序到構(gòu)造函數(shù)之外。 (參見(jiàn) StoreStore Barriers的說(shuō)明:在Store1;Store2之間插入StoreStore,確保Store1對(duì) 其他處理器可見(jiàn)(刷新內(nèi)存)先于Store2及所有后續(xù)存儲(chǔ)指令的存儲(chǔ))

writer方法中,實(shí)際上做了兩件事:

構(gòu)造了一個(gè)FinalDemo對(duì)象

把這個(gè)對(duì)象賦值給成員變量finalDemo

可能的執(zhí)行時(shí)序圖如下:


a,b之間沒(méi)有數(shù)據(jù)依賴性,普通域(普通變量)a可能會(huì)被重排序到構(gòu)造函數(shù)之外, 線程B就有可能讀到的是普通變量a初始化之前的值(零值),這樣就可能出現(xiàn)錯(cuò)誤。

final域變量b,根據(jù)重排序規(guī)則,會(huì)禁止final修飾的變量b重排序到構(gòu)造函數(shù)之外,從而b能夠正確賦值, 線程B就能夠讀到final變量初始化后的值。

因此,寫final域的重排序規(guī)則可以確保:在對(duì)象引用為任意線程可見(jiàn)之前,對(duì)象的final域已經(jīng)被正確初始化過(guò)了。 普通域不具有這個(gè)保障,比如在上例,線程B有可能就是一個(gè)未正確初始化的對(duì)象finalDemo。

讀final域重排序規(guī)則

讀final域重排序規(guī)則:在一個(gè)線程中,初次讀對(duì)象引用和初次讀該對(duì)象包含的final域,JMM會(huì)禁止這兩個(gè)操作的重排序。 (注意,這個(gè)規(guī)則僅僅是針對(duì)處理器), 處理器會(huì)在讀final域操作的前面插入一個(gè)LoadLoad屏障。 實(shí)際上,讀對(duì)象的引用和讀該對(duì)象的final域存在間接依賴性,一般處理器不會(huì)重排序這兩個(gè)操作。 但是有一些處理器會(huì)重排序,因此,這條禁止重排序規(guī)則就是針對(duì)這些處理器而設(shè)定的。

read方法主要包含了三個(gè)操作:

初次讀引用變量finalDemo

初次讀引用變量finalDemo的普通域a

初次讀引用變量finalDemo的final域b

假設(shè)線程A寫過(guò)程沒(méi)有重排序,那么線程A和線程B有一種的可能執(zhí)行時(shí)序如下:


讀對(duì)象的普通域被重排序到了讀對(duì)象引用的前面就會(huì)出現(xiàn)線程B還未讀到對(duì)象引用就在讀取該對(duì)象的普通域變量,這顯然是錯(cuò)誤的操作。

final域的讀操作就“限定”了在讀final域變量前已經(jīng)讀到了該對(duì)象的引用,從而就可以避免這種情況。

因此,讀final域的重排序規(guī)則可以確保:在讀一個(gè)對(duì)象的final域之前,一定會(huì)先讀這個(gè)包含這個(gè)final域的對(duì)象的引用。

final為引用類型
public class FinalReferenceDemo {    final int[] arrays; //arrays是引用類型
    private FinalReferenceDemo finalReferenceDemo;    public FinalReferenceDemo() {
        arrays = new int[1];  //1 
        arrays[0] = 1;        //2
    }    public void writerOne() {
        finalReferenceDemo = new FinalReferenceDemo(); //3
    }    public void writerTwo() {
        arrays[0] = 2;  //4
    }    public void reader() {        if (finalReferenceDemo != null) {  //5
            int temp = finalReferenceDemo.arrays[0];  //6
        }
    }
}

對(duì)final修飾的對(duì)象的成員域進(jìn)行寫操作

針對(duì)引用數(shù)據(jù)類型,final域?qū)戓槍?duì)編譯器和處理器重排序增加了這樣的約束: 在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final修飾的對(duì)象的成員域的寫入,與隨后在構(gòu)造函數(shù)之外把這個(gè)被構(gòu)造的對(duì)象的引用賦給一個(gè)引用變量,這兩個(gè)操作是不能被重排序的。 注意這里的是“增加”也就說(shuō)前面對(duì)final基本數(shù)據(jù)類型的重排序規(guī)則在這里還是使用。

線程線程A執(zhí)行wirterOne方法,執(zhí)行完后線程B執(zhí)行writerTwo方法,線程C執(zhí)行reader方法。 下圖就以這種執(zhí)行時(shí)序出現(xiàn)的一種情況來(lái)討論:


對(duì)final域的寫禁止重排序到構(gòu)造方法外,因此1和3不能被重排序。 由于一個(gè)final域的引用對(duì)象的成員域?qū)懭氩荒芘c在構(gòu)造函數(shù)之外將這個(gè)被構(gòu)造出來(lái)的對(duì)象賦給引用變量重排序, 因此2和3不能重排序。

對(duì)final修飾的對(duì)象的成員域進(jìn)行讀操作

JMM可以確保線程C至少能看到寫線程A對(duì)final引用的對(duì)象的成員域的寫入,即能看到arrays[0] = 1,而 寫線程B對(duì)數(shù)組元素的寫入可能看到可能看不到。 JMM不保證線程B的寫入對(duì)線程C可見(jiàn),線程B和線程C之間存在數(shù)據(jù)競(jìng)爭(zhēng),此時(shí)的結(jié)果是不可預(yù)知的。 如果想要可見(jiàn),可使用鎖或者volatile。

final重排序的總結(jié)


final寫final域讀
基本數(shù)據(jù)類型禁止final域?qū)懪c構(gòu)造方法重排序,即禁止final域?qū)懼嘏判虻綐?gòu)造方法之外,從而保證該對(duì)象對(duì)所有線程可見(jiàn)時(shí),該對(duì)象的final域全部已經(jīng)初始化過(guò)禁止初次讀對(duì)象的引用與讀該對(duì)象包含的final域的重排序,保證了在讀一個(gè)對(duì)象的final域之前,一定會(huì)先讀這個(gè)包含這個(gè)final域的對(duì)象的引用
引用數(shù)據(jù)類型額外增加約束:構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final修飾的對(duì)象的成員域的寫入,與隨后在構(gòu)造函數(shù)之外把這個(gè)被構(gòu)造的對(duì)象的引用賦給一個(gè)引用變量,這兩個(gè)操作是不能被重排序的
對(duì)象溢出

對(duì)象溢出:一種錯(cuò)誤的發(fā)布,當(dāng)一個(gè)對(duì)象還沒(méi)有構(gòu)造完成時(shí),就使它被其他線程所見(jiàn)。

/*** 對(duì)象溢出示例*/public class ThisEscape {
  public ThisEscape(EventSource source) {
    source.registerListener(new EventListener() {
      public void onEvent(Event e) {
        doSomething(e);
      }
    });
  }
 
  void doSomething(Event e) {
  }
}

這將導(dǎo)致this逸出,所謂逸出,就是在不該發(fā)布的時(shí)候發(fā)布了一個(gè)引用。

在這個(gè)例子里面,當(dāng)我們實(shí)例化ThisEscape對(duì)象時(shí),會(huì)調(diào)用source的registerListener方法, 這時(shí)便啟動(dòng)了一個(gè)線程,而且這個(gè)線程持有了ThisEscape對(duì)象(調(diào)用了對(duì)象的doSomething方法), 但此時(shí)ThisEscape對(duì)象卻沒(méi)有實(shí)例化完成(還沒(méi)有返回一個(gè)引用),所以我們說(shuō), 此時(shí)造成了一個(gè)this引用逸出,即還沒(méi)有完成的實(shí)例化ThisEscape對(duì)象的動(dòng)作,卻已經(jīng)暴露了對(duì)象的引用。

正確構(gòu)造過(guò)程:

public class SafeListener {
  private final EventListener listener;
 
  private SafeListener() {
    listener = new EventListener() {
      public void onEvent(Event e) {
        doSomething(e);
      }
    };
  }
 
  public static SafeListener newInstance(EventSource source) {
    SafeListener safe = new SafeListener();
    source.registerListener(safe.listener);
    return safe;
  }
 
  void doSomething(Event e) {
  }

當(dāng)構(gòu)造好了SafeListener對(duì)象(通過(guò)構(gòu)造器構(gòu)造)之后, 我們才啟動(dòng)了監(jiān)聽(tīng)線程,也就確保了SafeListener對(duì)象是構(gòu)造完成之后再使用的SafeListener對(duì)象。

結(jié)論:

只有當(dāng)構(gòu)造函數(shù)返回時(shí),this引用才應(yīng)該從線程中逸出。

構(gòu)造函數(shù)可以將this引用保存到某個(gè)地方,只要其他線程不會(huì)在構(gòu)造函數(shù)完成之前使用它。

final的實(shí)現(xiàn)原理

寫final域會(huì)要求編譯器在final域?qū)懼?,?gòu)造函數(shù)返回前插入一個(gè)StoreStore屏障。

讀final域的重排序規(guī)則會(huì)要求編譯器在讀final域的操作前插入一個(gè)LoadLoad屏障。

如果以X86處理為例,X86不會(huì)對(duì)寫-寫重排序,所以StoreStore屏障可以省略。 由于不會(huì)對(duì)有間接依賴性的操作重排序,所以在X86處理器中,讀final域需要的LoadLoad屏障也會(huì)被省略掉。 也就是說(shuō),以X86為例的話,對(duì)final域的讀/寫的內(nèi)存屏障都會(huì)被省略!具體是否插入還是得看是什么處理器。

注意:

上面對(duì)final域?qū)懼嘏判蛞?guī)則可以確保我們?cè)谑褂靡粋€(gè)對(duì)象引用的時(shí)候該對(duì)象的final域已經(jīng)在構(gòu)造函數(shù)被初始化過(guò)了。 但是這里其實(shí)是有一個(gè)前提條件: 在構(gòu)造函數(shù),不能讓這個(gè)被構(gòu)造的對(duì)象被其他線程可見(jiàn),也就是說(shuō)該對(duì)象引用不能在構(gòu)造函數(shù)中“溢出”。

public class FinalReferenceEscapeDemo {    private final int a;    private FinalReferenceEscapeDemo referenceDemo;    public FinalReferenceEscapeDemo() {
        a = 1;  //1
        referenceDemo = this; //2
    }    public void writer() {        new FinalReferenceEscapeDemo();
    }    public void reader() {        if (referenceDemo != null) {  //3
            int temp = referenceDemo.a; //4
        }
    }
}


假設(shè)一個(gè)線程A執(zhí)行writer方法另一個(gè)線程執(zhí)行reader方法。因?yàn)闃?gòu)造函數(shù)中操作1和2之間沒(méi)有數(shù)據(jù)依賴性,1和2可以重排序,先執(zhí)行了2,這個(gè)時(shí)候引用對(duì)象referenceDemo是個(gè)沒(méi)有完全初始化的對(duì)象,而當(dāng)線程B去讀取該對(duì)象時(shí)就會(huì)出錯(cuò)。盡管依然滿足了final域?qū)懼嘏判蛞?guī)則:在引用對(duì)象對(duì)所有線程可見(jiàn)時(shí),其final域已經(jīng)完全初始化成功。 但是,引用對(duì)象“this”逸出,該代碼依然存在線程安全的問(wèn)題。



文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/7164.html

相關(guān)文章

  • java 基礎(chǔ) - 收藏集 - 掘金

    摘要:基礎(chǔ)知識(shí)復(fù)習(xí)后端掘金的作用表示靜態(tài)修飾符,使用修飾的變量,在中分配內(nèi)存后一直存在,直到程序退出才釋放空間。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 Java 學(xué)習(xí)過(guò)程|完整思維導(dǎo)圖 - 后端 - 掘金JVM 1. 內(nèi)存模型( 內(nèi)存分為幾部分? 堆溢出、棧溢出原因及實(shí)例?線上如何排查?) 2. 類加載機(jī)制 3. 垃圾回收 Java基礎(chǔ) 什么是接口?什么是抽象...

    makeFoxPlay 評(píng)論0 收藏0
  • Java面試,深入理解final關(guān)鍵字

    摘要:寫域重排序規(guī)則寫域的重排序規(guī)則禁止對(duì)域的寫重排序到構(gòu)造函數(shù)之外,這個(gè)規(guī)則的實(shí)現(xiàn)主要包含了兩個(gè)方面禁止編譯器把域的寫重排序到構(gòu)造函數(shù)之外編譯器會(huì)在域?qū)懼?,?gòu)造函數(shù)之前,插入一個(gè)屏障。結(jié)論只有當(dāng)構(gòu)造函數(shù)返回時(shí),引用才應(yīng)該從線程中逸出。final關(guān)鍵字final的簡(jiǎn)介final可以修飾變量,方法和類,用于表示所修飾的內(nèi)容一旦賦值之后就不會(huì)再被改變,比如String類就是一個(gè)final類型的類。f...

    番茄西紅柿 評(píng)論0 收藏0
  • Java面試,深入理解final關(guān)鍵字

    摘要:寫域重排序規(guī)則寫域的重排序規(guī)則禁止對(duì)域的寫重排序到構(gòu)造函數(shù)之外,這個(gè)規(guī)則的實(shí)現(xiàn)主要包含了兩個(gè)方面禁止編譯器把域的寫重排序到構(gòu)造函數(shù)之外編譯器會(huì)在域?qū)懼螅瑯?gòu)造函數(shù)之前,插入一個(gè)屏障。結(jié)論只有當(dāng)構(gòu)造函數(shù)返回時(shí),引用才應(yīng)該從線程中逸出。final關(guān)鍵字final的簡(jiǎn)介final可以修飾變量,方法和類,用于表示所修飾的內(nèi)容一旦賦值之后就不會(huì)再被改變,比如String類就是一個(gè)final類型的類。f...

    Michael_Ding 評(píng)論0 收藏0
  • 后臺(tái)開(kāi)發(fā)常問(wèn)面試集錦(問(wèn)搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問(wèn)題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問(wèn)題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    spacewander 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<