国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Java面向?qū)ο笏枷胍约霸硪约皟?nèi)存圖解

這篇具有很好參考價值的文章主要介紹了Java面向?qū)ο笏枷胍约霸硪约皟?nèi)存圖解。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

什么是面向?qū)ο?/h2>

面向?qū)ο蠛兔嫦蜻^程區(qū)別

面向過程:面向過程是將解決問題的思路轉(zhuǎn)為一個個方法。
面向?qū)ο?/strong>:面向?qū)ο髣t是編寫一個對象,將這些思路封裝成一個個對象方法,后續(xù)調(diào)用這個對象解決問題,相對面向過程而言,這種思路更符合人的思維并且更易擴(kuò)展、復(fù)用、維護(hù)。

面向?qū)ο蠛兔嫦蜻^程性能差距:人們常常認(rèn)為面向過程性能高于面向?qū)ο?,因為?chuàng)建的對象開銷遠(yuǎn)遠(yuǎn)大于面向過程,實際上Java面向?qū)ο笮阅懿畹脑虿⒉皇沁@個,真正的原因是Java為半編譯語言,運(yùn)行并不是直接拿著二進(jìn)制機(jī)械碼執(zhí)行,而是需要結(jié)果字節(jié)碼轉(zhuǎn)換這一步。
而且面向過程的性能并不一定比面向過程快,面向過程也需要計算偏移量以及某些腳本語言的性能也一定比Java好。

創(chuàng)建一個對象用什么運(yùn)算符?

用new運(yùn)算符,創(chuàng)建的對象的實例會在堆內(nèi)存中開辟一個空間。而引用則在棧內(nèi)存中,指向?qū)ο髮嵗?/p>

面向?qū)ο髮崿F(xiàn)偽代碼

以人開門為例,人需要開門,所以我們需要創(chuàng)建一個門對象,描述門的特征,這個門可以開或者關(guān)。所以門的偽代碼如下:

門{
	開()
	{
	操作門軸
	}
}

上文說到了,面向?qū)ο蟮奶攸c就是符合人的思維,而人開門這個功能,我們就可以創(chuàng)建一個人的對象,編寫一個開門的動作,把門打開。通過這種對象調(diào)用對象的方式完成了功能。后續(xù)我們需要狗開門,貓開門也只是編寫一個方法調(diào)用門對象的開的動作。

{
	開門(門對象){.()
	}
 }


面向?qū)ο笕筇卣?/h3>
  1. 封裝
  2. 繼承
  3. 多態(tài)

類和對象的關(guān)系。

以生活事務(wù)為例,現(xiàn)實生活中的對象:張三 李四。他們都有姓名、性別、學(xué)習(xí)Java的能力。

所以我們要想通過面向?qū)ο笏枷雽崿F(xiàn)抽象出對象,就得提取共性,編寫一個類有姓名、性別、學(xué)習(xí)Java的能力。

public class Student {
    
    private String name;
    private int sex;
    
    public void studyJava(){
        System.out.println(this.name+"學(xué)習(xí)java");
    }
}

描述時,這些對象的共性有:姓名,年齡,性別,學(xué)習(xí)java功能。再將這些分析映射到j(luò)ava中,就是以class定義的類進(jìn)行展開。

public static void main(String[] args) {
        Student zhangsan=new Student();
        zhangsan.setName("張三");
        zhangsan.studyJava();

        Student lisi=new Student();
        lisi.setName("李四");
        lisi.studyJava();
        
//        輸出結(jié)果
//        張三學(xué)習(xí)java
//                李四學(xué)習(xí)java
    }

而具體對象就是對應(yīng)java在堆內(nèi)存中用new建立實體。

基礎(chǔ)案例

需求:描述汽車(顏色,輪胎數(shù))。描述事物其實就是在描述事物的屬性和行為。

屬性對應(yīng)在類中即變量行為對應(yīng)的類中的函數(shù)(方法)。

代碼實現(xiàn)

public class Car {
    //描述顏色
    String color = "紅色";
    //描述輪胎數(shù)
    int num = 4;

    //運(yùn)行行為。
    public void run() {

        System.out.println("顏色:"+color + " 輪胎數(shù):" + num);
    }
}

實例化

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }
}

創(chuàng)建car對象時car引用的內(nèi)存圖

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

對象調(diào)用方法過程

首先我們看一段代碼,這是一個人類的class類代碼

public class Person {
    private String name;
    private int age;
    private static String country = "cn";

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static void showCountry() {
        System.out.println("showCountry " + country);
    }


    public void speak() {
        System.out.println(this.getName() + " speak");
    }
}

假如我們在main中編寫這樣一段代碼,請問在內(nèi)存中他是如何工作的呢?

 public static void main(String[] args) {
        Person p = new Person("張三", 18);
        p.setName("李四");
    }

我們先從類類加載時開始分析,由于static關(guān)鍵字修改的變量或者方法會隨著jvm加載類時一起創(chuàng)建,所以countryshowCountry()在方法區(qū)是這樣的。

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

然后main方法開始執(zhí)行對應(yīng)代碼,首先main方法入棧,初始化一個p引用

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

堆區(qū)開辟一個空間,創(chuàng)建一個person實例,p引用指向這個內(nèi)存空間

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

調(diào)用setName,setName入棧,完成name值修改之后銷毀

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

成員變量和局部變量

作用范圍

成員變量作用于整個類中。
局部變量變量作用于函數(shù)中,或者語句中。

在內(nèi)存中的位置

成員變量:在堆內(nèi)存中,因為對象的存在,才在內(nèi)存中存在。
局部變量:存在棧內(nèi)存中。

關(guān)于對象的引用關(guān)系

簡介

對象引用用于指向0個或者多個對象實例,對象實例可以被多個對象引用指向。

相關(guān)代碼

假如我們使用上文car類執(zhí)行以下代碼,那么在內(nèi)存中會如何執(zhí)行呢?

car c=new car();
c.num=5;
car c1=c;
c.run();

內(nèi)存圖解

  1. 首先堆區(qū)開辟一個空間創(chuàng)建car對象,初始化值
  2. 修改num為5
  3. c1引用指向c,如下圖所示
    匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

對象相等和引用相等的區(qū)別

  1. 對象相等:兩個對象內(nèi)存中的存放的內(nèi)容都相等
  2. 引用相等:兩個引用指向的內(nèi)存地址相等。

類的構(gòu)造方法的作用是什么

完成對象初始化,首先在堆區(qū)創(chuàng)建對象實例。

構(gòu)造方法的特點

  1. 與類名相同
  2. 無返回值
  3. 生成對象時自動執(zhí)行
  4. 不可重寫可以重載

深拷貝和淺拷貝區(qū)別

淺拷貝

對象進(jìn)行拷貝時,如果內(nèi)部有引用類型,克隆對象僅僅是復(fù)制被克隆內(nèi)部對象的引用地址

為了介紹淺拷貝我們貼出這樣一段代碼,可以看到一個學(xué)生類有id和name,以及一個Vector的引用對象

public class Student implements Cloneable {
    private String id;
    private String name;
    private Vector<String> vector;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Vector<String> getVector() {
        return vector;
    }

    public void setVector(Vector<String> vector) {
        this.vector = vector;
    }


    public Student() {
        try {
            System.out.println("創(chuàng)建對象需要三秒......");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public Student newInstance() {
        try {
            return (Student) this.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return null;
    }

  
}

然后我們使用下面這段代碼進(jìn)行測試,可以看到輸出結(jié)果為true,說明student2的vector是student1的。如下圖所示,克隆對象的內(nèi)部引用對象和student1是通用的

@Test
    public void cloneTest() throws CloneNotSupportedException {

        long start,end;
        start=System.currentTimeMillis();
        Student student=new Student();
        end=System.currentTimeMillis();
        System.out.println("學(xué)生1創(chuàng)建時間長 "+(end-start));


        student.setId("1");
        student.setName("小明");
        Vector<String> v = new Vector<>();
        v.add("000000");
        v.add("000001");
        student.setVector(v);

        start=System.currentTimeMillis();
        Student student2= student.newInstance();
        end=System.currentTimeMillis();
        System.out.println("學(xué)生2創(chuàng)建時間長 "+(end-start));


        for (String s : student2.getVector()) {
            System.out.println(s);
        }
//        false則說明深拷貝成功
        System.out.println(student.getVector()==student2.getVector());
    }

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

深拷貝

了解了淺拷貝之后,我們就可以解釋深拷貝了,克隆對象的內(nèi)部引用對象都是全新復(fù)制出來的一份

基于上文student代碼我們對此進(jìn)行改造,重寫以下clone方法

@Override
    protected Object clone() throws CloneNotSupportedException {
        Student clone = new Student();
        clone.setId(this.getId());
        clone.setName(this.getName());

        //避免clone導(dǎo)致淺拷貝問題
        Vector<String> srcVector = this.getVector();

        Vector<String> dstVector = new Vector<>();
        for (String v : srcVector) {
            dstVector.add(v);
        }

        clone.setVector(dstVector);

        return clone;

    }

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

匿名對象

實例代碼

如下所示,在堆區(qū)創(chuàng)建一個對象實例,用后即被銷毀。為了介紹匿名對象,我們首先需要編寫一個汽車類

public class Car {
    //描述顏色
    private String color = "紅色";
    //描述輪胎數(shù)
    private int num = 4;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    //運(yùn)行行為。
    public void run() {
        this.setNum(++num);
        System.out.println("顏色:" + color + " 輪胎數(shù):" + num);
    }
}

然后我們使用測試單元進(jìn)行測試


@Test
    public void anonymously(){
        new Car().run();

    }
		

上述代碼的工作過程如下所示,可以看到完成方法調(diào)用之后

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

匿名對象與實例對象的區(qū)別

實例代碼

可以看到我們現(xiàn)實創(chuàng)建一個非匿名的汽車類和匿名汽車類,并作為show方法的參數(shù)傳入


public static void main(String[] args) {
        Car car = new Car();
        show(car);
        show(new Car());
        /**
         * 輸出結(jié)果
         * 顏色:black 輪胎數(shù):4
         * 顏色:black 輪胎數(shù):4
         */
    }


    public static void show(Car c) {
        c.setNum(3);
        c.setColor("black");
        c.run();
    }

圖解匿名與非匿名內(nèi)存運(yùn)行

非匿名對象內(nèi)存運(yùn)行過程圖解

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

匿名對象完成方法調(diào)用后即被銷毀

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

使用場景

  1. 當(dāng)對對象的方法只調(diào)用一次時,可以用匿名對象來完成,這樣寫比較簡化。如果對一個對象進(jìn)行多個成員調(diào)用,必須給這個對象起個名字。

  2. 可以將匿名對象作為實際參數(shù)進(jìn)行傳遞。

封裝

什么是封裝

以生活為例子,某公司老板招開發(fā)人員,招得開發(fā)人員后,開發(fā)人員工作過程不用看到,老板只關(guān)注開發(fā)結(jié)果,而老板只看到開發(fā)結(jié)果這一現(xiàn)象即封裝。

什么時private修飾

private :私有,權(quán)限修飾符:用于修飾類中的成員(成員變量,成員函數(shù))。私有只在本類中有效。

代碼示例

如下所示,setAge就是對age賦值的封裝,隱藏對年齡操作的細(xì)節(jié),用戶只需通過這個方法完成自己需要的賦值動作即可

public class Person {
    private int age;

    public void setAge(int a) {
        if (a > 0 && a < 130) {
            age = a;
            speak();
        } else
            System.out.println("feifa age");
    }

    public int getAge() {
        return age;
    }

    private void speak() {
        System.out.println("age=" + age);
    }
}

構(gòu)造函數(shù)

什么是構(gòu)造函數(shù)

  1. 對象一建立就會調(diào)用與之對應(yīng)的構(gòu)造函數(shù)。
  2. 構(gòu)造函數(shù)的作用:可以用于給對象進(jìn)行初始化。

構(gòu)造函數(shù)的小細(xì)節(jié)

  1. 類默認(rèn)有構(gòu)造函數(shù),顯示創(chuàng)建后默認(rèn)構(gòu)造類就消失。
  2. 默認(rèn)構(gòu)造函數(shù)權(quán)限和類權(quán)限修飾符一致,例如類權(quán)限為public,則構(gòu)造方法默認(rèn)也為public,除非顯示修改權(quán)限。

構(gòu)造代碼塊

構(gòu)造代碼塊示例以及與構(gòu)造方法的區(qū)別

構(gòu)造代碼塊。

作用:給對象進(jìn)行初始化。
對象一建立就運(yùn)行,而且優(yōu)先于構(gòu)造函數(shù)執(zhí)行。
和構(gòu)造函數(shù)的區(qū)別:
1. 構(gòu)造代碼塊是給所有對象進(jìn)行統(tǒng)一初始化,在jvm完成類加載時就會運(yùn)行方法,也就是說調(diào)用靜態(tài)方法的情況下,構(gòu)造代碼塊也會被執(zhí)行
2. 而構(gòu)造函數(shù)是給對應(yīng)的對象初始化。

構(gòu)造代碼塊會隨著對象實例的創(chuàng)建和運(yùn)行。

public class Person {
    private String name;
    private int age;


    {
        System.out.println("person 類的構(gòu)造代碼塊執(zhí)行了");
        run();
    }
    public void run(){
        System.out.println("person run");
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

如下便是main函數(shù)的運(yùn)行結(jié)果

public static void main(String[] args) {
        Person p=new Person();
        /**
         * person 類的構(gòu)造代碼塊執(zhí)行了
         * person run
         */
    }

this關(guān)鍵字

什么是this關(guān)鍵字

代表它所在函數(shù)所屬對象的引用。簡單來說,調(diào)用對象方法的對象就是this關(guān)鍵字多代表的對象。

this的應(yīng)用

解決構(gòu)造函數(shù)初始化的問題

如下代碼,假如所有成員變量不加this,編譯器則不會找成員變量name,導(dǎo)致賦值過程毫無意義。

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言
輸出結(jié)果

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

對此我們就可以使用this關(guān)鍵字即可解決問題

public class Person {
    private String name;
    private int age;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

用于構(gòu)造函數(shù)之間進(jìn)行互相調(diào)用

注意:this語句只能定義在構(gòu)造函數(shù)的第一行。

public Person(String name, int age) {
        this(name);
        this.age = age;
    }

    public Person(String name) {
        this.name = name;
    }

static關(guān)鍵字

什么是static關(guān)鍵字

用法:是一個修飾符,用于修飾成員(成員變量,成員函數(shù)).
當(dāng)成員被靜態(tài)修飾后,就多了一個調(diào)用方式,除了可以被對象調(diào)用外,還可以直接被類名調(diào)用。類名.靜態(tài)成員。

static特點

  1. 隨著類的加載而加載。隨著jvm完成類加載該變量或者方法就會被加載到方法區(qū)。
  2. 靜態(tài)會隨著類的消失而消失。說明它的生命周期最長。
  3. 優(yōu)先于對象存在,即靜態(tài)變量先存在,對象后存在。
  4. 被所有對象所共享,所以有時候我們需要考慮線程安全問題。
  5. 可以直接被類名所調(diào)用。

實例變量和類變量的區(qū)別

  1. 實例變量隨著對象的創(chuàng)建而存放在堆內(nèi)存上,而類變量即靜態(tài)變量隨著類加載而存放在方法區(qū)上。
  2. 實例變量隨著對象實例消亡而消亡,而類變量隨著類的消亡和消亡。

靜態(tài)使用注意事項:

  1. 靜態(tài)方法只能訪問靜態(tài)變量,實例對象則靜態(tài)非靜態(tài)都可以訪問
  2. 靜態(tài)方法不可使用this和super關(guān)鍵字,因為this和super都是對象實例的關(guān)鍵字,this關(guān)鍵字是指向?qū)ο髮嵗瑂tatic關(guān)鍵字在類加載時候就能被指向,故不可使用這兩個關(guān)鍵字。

靜態(tài)有利有弊

利處

隨著類加載而創(chuàng)建,每個對象公用一份,無需每個實例到堆區(qū)創(chuàng)建一份。

弊處

  1. 生命周期長
  2. 使用不當(dāng)可能造成線程安全問題。
  3. 訪問有局限性,靜態(tài)方法只能訪問靜態(tài)相關(guān)變量或者方法。

錯誤代碼示范

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

圖解對象如何調(diào)用static變量

我們首先編寫這樣一段代碼

public class Person {
    private String name;
    private int age;

    public static int staticVar=4;
    
   

}

然后我們的main方法進(jìn)行這樣的調(diào)用,在jvm內(nèi)存是如何執(zhí)行的呢?

 public static void main(String[] args) {
       Person person=new Person();
       person.staticVar=5;
               
    }
  1. main方法入棧
  2. 堆區(qū)創(chuàng)建person對象實例,p指向?qū)嵗?/li>
  3. p實例通過堆區(qū)對象操作方法的靜態(tài)變量,修改值為5

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

main函數(shù)

主函數(shù)

主函數(shù)是一個特殊的函數(shù),程序執(zhí)行的入口,可以被jvm執(zhí)行。

主函數(shù)的定義格式以及關(guān)鍵字含義

public:代表著該函數(shù)訪問權(quán)限是最大的。
static:代表主函數(shù)隨著類的加載就已經(jīng)存在了。
void:主函數(shù)沒有具體的返回值。
main:不是關(guān)鍵字,但是是一個特殊的單詞,可以被jvm識別。
(String[] args):函數(shù)的參數(shù),參數(shù)類型是一個數(shù)組,該數(shù)組中的元素是字符串。字符串類型的數(shù)組。

主函數(shù)是固定格式的

只有符合上述的固定格式,jvm才能識別。

jvm在調(diào)用主函數(shù)時,傳入的是new String[0];即可長度為0的String

如何使用args

class MainDemo 
{
	public static void main(String[] args)//new String[]
	{
		String[] arr = {"hah","hhe","heihei","xixi","hiahia"};

		MainTest.main(arr);
	}
}


class MainTest
{
	public static void main(String[] args)
	{
		for(int x=0; x<args.length; x++)
			System.out.println(args[x]);
	}
}

靜態(tài)代碼塊

格式

static
{
	靜態(tài)代碼塊中的執(zhí)行語句。
}

我們在person類中編寫一個靜態(tài)代碼塊,然后調(diào)用其他靜態(tài)方法,可以發(fā)現(xiàn)靜態(tài)代碼塊會隨著類的加載而完成執(zhí)行,并且只執(zhí)行一次

public class Person {
    

    static {
        System.out.println("靜態(tài)代碼塊,隨著方法執(zhí)行而執(zhí)行.....");
    }

    public static void func(){
        System.out.println("靜態(tài)方法執(zhí)行了");
    }
    
}

main方法調(diào)用示例

 public static void main(String[] args) {
       Person.func();
       Person.func();
        /**
         * 輸出結(jié)果
         * 靜態(tài)代碼塊,隨著方法執(zhí)行而執(zhí)行.....
         * 靜態(tài)方法執(zhí)行了
         * 靜態(tài)方法執(zhí)行了
         */

    }

設(shè)計優(yōu)化

單例模式

簡介

對于重量級對象的創(chuàng)建可能會導(dǎo)致以下問題:

  1. 創(chuàng)建對象開銷大
  2. GC壓力大,可能導(dǎo)致系統(tǒng)卡頓

餓漢式

代碼如下所示,可以看到對象隨著類的加載就會立刻完成創(chuàng)建,這就導(dǎo)致假如我們使用這個類的某些不需要單例的方法也會完成對象的創(chuàng)建。
例如我們就像調(diào)用以下Singleton 的sayHello這個靜態(tài)方法就會導(dǎo)致單例實例被創(chuàng)建,所以如果非必要我們不建議采用這種非延遲加載的單例模式

public class Singleton {
    private Singleton() {
        System.out.println("創(chuàng)建單例");
    }

    public static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    public static void sayHello(){
        System.out.println("hello");
    }

    
}

測試代碼,可以看到調(diào)用靜態(tài)方法單例就被被創(chuàng)建了

public static void main(String[] args) {

        Singleton.sayHello();
        /**
         * 輸出:
         * 創(chuàng)建單例
         * hello
         */

    }

原理也很簡單,靜態(tài)變量和方法都在方法區(qū),隨著類被加載這些變量或者方法都會被加載。

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

懶漢式(線程不安全)

懶漢式即實現(xiàn)延遲加載的有效手段,代碼如下所示

/**
 * 延遲加載的單例類 避免jvm加載時創(chuàng)建對象
 */
public class LazySingleton {
    private LazySingleton() {
        System.out.println("懶加載單例類");
    }

    private static LazySingleton instance = null;

    
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    public static void sayHello(){
        System.out.println("hello");
    }

   
}

調(diào)用示例如下所示,可以看到靜態(tài)方法調(diào)用后并沒有創(chuàng)建實例,只有調(diào)用獲取對象時才會得到對象實例

public static void main(String[] args) {
        LazySingleton.sayHello();
        LazySingleton.getInstance();
        /**
         * 輸出結(jié)果
         * hello
         * 懶加載單例類
         */
    }

懶漢式的工作原理如下圖所示,可以看到只有調(diào)用getInstance后,才會在堆內(nèi)存中開辟一塊內(nèi)存空間創(chuàng)建對象

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

實際上,當(dāng)前的懶漢式存在線程安全問題,如上內(nèi)存圖解所示,可能會有兩個線程走到==null的判斷中進(jìn)而出現(xiàn)創(chuàng)建多個單例對象的情況。我們使用JUC的倒計時門閂調(diào)用獲取單例的情況,可以看到對象被創(chuàng)建了多次。

 /**
     * 不加synchronized的懶加載 加上則沒有下面這樣輸出結(jié)果
     */
    @Test
    public void threadTest0(){
        ExecutorService threadPool = Executors.newFixedThreadPool(1000);
        CountDownLatch countDownLatch=new CountDownLatch(1);
        for (int i = 0; i < 10000; i++) {
            threadPool.submit(()->{
                System.out.println(LazySingleton.getInstance());
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }


        countDownLatch.countDown();
        /**
         * 懶加載單例類
         * 懶加載單例類
         * 懶加載單例類
         * 懶加載單例類
         * com.optimize.design.LazySingleton@12423874
         * com.optimize.design.LazySingleton@350c55ec
         * com.optimize.design.LazySingleton@350c55ec
         * com.optimize.design.LazySingleton@350c55ec
         * 懶加載單例類
         * com.optimize.design.LazySingleton@350c55ec
         * com.optimize.design.LazySingleton@5897fc07
         * 懶加載單例類
         * com.optimize.design.LazySingleton@5897fc07
         * 懶加載單例類
         * com.optimize.design.LazySingleton@39d8305
         * com.optimize.design.LazySingleton@1a0eae7f
         * com.optimize.design.LazySingleton@1a0eae7f
         * 懶加載單例類
         * com.optimize.design.LazySingleton@1a0eae7f
         * 懶加載單例類
         */
    }

懶漢式(線程安全)

要想實現(xiàn)線程安全,我們只需要通過下面這種方式上鎖即可保線程安全,但是缺點也很明顯,在高并發(fā)情況下,獲取對象的實踐會隨著增加

 /**
     * 增加 synchronized確保線程安全
     * @return
     */
    public synchronized static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

測試用例如下,可以看到餓漢式和線程安全懶漢式時間的差距

@Test
    public void test(){
        long start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            Singleton.getInstance();
        }
        long end=System.currentTimeMillis();
        System.out.println(end-start);


         start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            LazySingleton.getInstance();
        }
         end=System.currentTimeMillis();
        System.out.println(end-start);





        /**
         * 輸出結(jié)果
         *
         * 創(chuàng)建單例
         * 3
         * 懶加載單例類
         * 20
         */
    }

內(nèi)部類模式

上文提到的懶漢式的性能問題,所以我們可以使用內(nèi)部類模式解決該問題,代碼如下所示,可以看到我們在單例類的內(nèi)部增加一個靜態(tài)內(nèi)部類,該類被加載時靜態(tài)內(nèi)部類并不會被加載,只有調(diào)用getInstance才會創(chuàng)建單例對象,并且該對象的創(chuàng)建是隨著類的加載就完成創(chuàng)建,故這是一種線程友好的單例模式

/**
 * 線程安全的單例 但還是會被反射攻破
 */
public class StaticSingleton {
    private StaticSingleton() {
        System.out.println("靜態(tài)內(nèi)部類延遲加載");
    }

    private static class SingletonHolder {
        private static StaticSingleton instance = new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return SingletonHolder.instance;
    }

    public static void sayHello(){
        System.out.println("hello");
    }

    
}

測試代碼和輸出結(jié)果

public static void main(String[] args) {
        StaticSingleton.sayHello();
        StaticSingleton.getInstance();
        /**
         * 輸出結(jié)果
         * 
         * hello
         * 靜態(tài)內(nèi)部類延遲加載
         */
    }

內(nèi)部類單例模式工作過程

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

實際上這種模式也有缺點,就是會被發(fā)射攻破,后續(xù)我們會介紹對應(yīng)的解決方案

雙重鎖校驗(線程安全)

雙重鎖校驗的單例模式如下所示,可以看到雙重鎖校驗的編碼方式和簡單,第一次判斷避免沒必要的執(zhí)行,第二次判斷避免第一次判定為空走到創(chuàng)建對象代碼塊的線程,從而避免線程安全問題

public class DoubleCheckLockSingleton {

    private static DoubleCheckLockSingleton instance = null;

    private DoubleCheckLockSingleton() {
        System.out.println("雙重鎖單例對象被創(chuàng)建");
    }

    public static DoubleCheckLockSingleton getInstance() {
        if (instance != null) {
            return instance;
        }

        synchronized (DoubleCheckLockSingleton.class) {
            //這一重校驗是為了避免上面判空后進(jìn)入休眠走到這個代碼塊的線程
            if (null == instance) {
                instance = new DoubleCheckLockSingleton();
                return instance;
            }
        }
        return instance;
    }


}

性能上我們可以看到雙重鎖校驗的性能要好于靜態(tài)內(nèi)部類的方式

 @Test
    public void test(){
        long start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            Singleton.getInstance();
        }
        long end=System.currentTimeMillis();
        System.out.println(end-start);


         start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            LazySingleton.getInstance();
        }
         end=System.currentTimeMillis();
        System.out.println(end-start);


        start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            StaticSingleton.getInstance();
        }
        end=System.currentTimeMillis();
        System.out.println(end-start);


        start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            DoubleCheckLockSingleton.getInstance();
        }
        end=System.currentTimeMillis();
        System.out.println(end-start);


        /**
         * 創(chuàng)建單例
         * 6
         * 懶加載單例類
         * 20
         * 靜態(tài)內(nèi)部類延遲加載
         * 4
         * 雙重鎖單例對象被創(chuàng)建
         * 3
         */
    }

枚舉單例模式(線程安全)

/**
 * 使用枚舉保證類單例
 */
public enum Elvis {
    INSTANCE;
    private String name="elvis";

    public String getName() {
        return name;
    }

    public static Elvis getInstance(){
        return INSTANCE;
    }


    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }


}

可以看到這種方式不會被反射攻破

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<Elvis> elvisClass = Elvis.class;
        Elvis elvis1 = elvisClass.newInstance();
        System.out.println(elvis1.getName()); 

    }

輸出結(jié)果

匿名內(nèi)存和非匿名內(nèi)存,Java,java,封裝,多態(tài),類,編程語言

相關(guān)面試題

下面這段代碼。new StaticCode(4)的輸出結(jié)果?

public class StaticCode {
    int num = 9;

    StaticCode() {
        System.out.println("b");
    }

    static {
        System.out.println("a");
    }

    {
        System.out.println("c" + this.num);
    }

    StaticCode(int x) {
        System.out.println("d");
    }

    public static void show() {
        System.out.println("show run");
    }
}

答案:a c9 d

創(chuàng)建類,加載順序為:

  1. 加載靜態(tài)代碼塊
  2. 加載構(gòu)造代碼塊
  3. 加載構(gòu)造方法

參考文獻(xiàn)

Java基礎(chǔ)常見面試題總結(jié)(中)

Effective Java中文版(第3版)

Java系統(tǒng)性能優(yōu)化實戰(zhàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-757735.html

到了這里,關(guān)于Java面向?qū)ο笏枷胍约霸硪约皟?nèi)存圖解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【JavaSE】面向?qū)ο缶幊趟枷胫^承

    【JavaSE】面向?qū)ο缶幊趟枷胫^承

    ?【本節(jié)目標(biāo)】 1. 繼承 2. 組合 目錄 1.?為什么需要繼承 2.?繼承概念 3.?繼承的語法 4.?父類成員訪問 4.1 子類中訪問父類的成員變量 4.2 子類中訪問父類的成員方法 5. super 6.?子類構(gòu)造方法 7. super和this 8. 再談初始化 9. protected 10. 繼承方式 11. final 12 繼承與

    2024年02月12日
    瀏覽(25)
  • 【JavaSE】面向?qū)ο缶幊趟枷胫鄳B(tài)(圖文詳解)

    【JavaSE】面向?qū)ο缶幊趟枷胫鄳B(tài)(圖文詳解)

    目錄 1.?多態(tài)的概念 2. 多態(tài)實現(xiàn)條件 3. 重寫 4. 向上轉(zhuǎn)型和向下轉(zhuǎn)型 4.1 向上轉(zhuǎn)型 4.2 向下轉(zhuǎn)型 5. 多態(tài)的優(yōu)缺點 6. 避免在構(gòu)造方法中調(diào)用重寫的方法 多態(tài)的概念:通俗來說,就是多種形態(tài), 具體點就是去完成某個行為,當(dāng)不同的對象去完成時會產(chǎn)生出不同的狀態(tài)。 ?總的來說

    2024年02月14日
    瀏覽(31)
  • java面向?qū)ο蟆^承以及super關(guān)鍵字

    java面向?qū)ο蟆^承以及super關(guān)鍵字

    在 同?類 中,存在?法名相同,參數(shù)列表不同(個數(shù)、類型或順序不同)的?法互為重載。 在 繼承關(guān)系 中,?類聲明?個繼承??類的?法名相同、參數(shù)列表相同,返回值類型?致,訪問修飾符相同或 變?,拋出異常相同或縮?的?法稱為重寫。 重載是編譯時多態(tài),重寫

    2024年02月12日
    瀏覽(16)
  • 數(shù)據(jù)可視化——結(jié)合面向?qū)ο蟮乃枷雽崿F(xiàn)數(shù)據(jù)可視化

    數(shù)據(jù)可視化——結(jié)合面向?qū)ο蟮乃枷雽崿F(xiàn)數(shù)據(jù)可視化

    前面我們已經(jīng)學(xué)習(xí)了如何使用 python 的 pyecharts 模塊來實現(xiàn)數(shù)據(jù)可視化,將數(shù)據(jù)經(jīng)過處理后以折線圖、地圖以及柱狀圖的形式展現(xiàn)出來,那么這篇文章我將以一個例子為大家分享如何結(jié)合 面向?qū)ο?的思想來實現(xiàn)數(shù)據(jù)可視化。 收集數(shù)據(jù):收集需要進(jìn)行可視化的數(shù)據(jù),并確保數(shù)據(jù)

    2024年02月16日
    瀏覽(24)
  • Java面向?qū)ο蟆鄳B(tài)、Object類、instanceof關(guān)鍵字以及final關(guān)鍵字

    Java面向?qū)ο蟆鄳B(tài)、Object類、instanceof關(guān)鍵字以及final關(guān)鍵字

    總之,多態(tài)是面向?qū)ο缶幊讨幸粋€非常重要的概念,通過它可以實現(xiàn)統(tǒng)一的接口來操作不同的對象,提高代碼的可讀性和可維護(hù)性。在實際編程中,多態(tài)性的使用可以使代碼更加靈活和擴(kuò)展性更強(qiáng)。方法重寫是實現(xiàn)多態(tài)的基礎(chǔ)。 重寫如下所示 關(guān)于hashCode方法的重寫: 重寫t

    2024年02月12日
    瀏覽(114)
  • 創(chuàng)造與布局:剖析 Java 對象創(chuàng)建過程以及內(nèi)存布局

    創(chuàng)造與布局:剖析 Java 對象創(chuàng)建過程以及內(nèi)存布局

    目錄 上下文提及到了類的加載過程,詳細(xì)介紹了加載類的每個階段:Loading、Linking、Initialize,在其中也說明了靜態(tài)變量賦值順序 先賦予默認(rèn)值、在 Initialize 初始化階段賦予初始值 從類加載到雙親委派:深入解析類加載機(jī)制與 ClassLoader 該篇文章會詳細(xì)實例對象的創(chuàng)建過程、對

    2024年02月11日
    瀏覽(23)
  • 【IMX6ULL驅(qū)動開發(fā)學(xué)習(xí)】11.驅(qū)動設(shè)計之面向?qū)ο骭分層思想(學(xué)習(xí)設(shè)備樹過渡部分)

    【IMX6ULL驅(qū)動開發(fā)學(xué)習(xí)】11.驅(qū)動設(shè)計之面向?qū)ο骭分層思想(學(xué)習(xí)設(shè)備樹過渡部分)

    一個 可移植性好 的驅(qū)動程序,應(yīng)該有三個部分組成 1、驅(qū)動框架程序(xxx_drv.c) — 對接應(yīng)用層的 open read write 函數(shù),不做GPIO具體操作 2、硬件操作程序(xxx_chip_gpio.c)— 執(zhí)行具體的GPIO操作,初始化、讀寫 3、硬件資源定義程序(xxx_board.c,這在之后就過渡成了設(shè)備樹)— 為

    2024年02月11日
    瀏覽(25)
  • 面向?qū)ο蟮慕榻B和內(nèi)存

    面向?qū)ο蟮慕榻B和內(nèi)存

    學(xué)習(xí)面向?qū)ο髢?nèi)容的三條主線 ? Java 類及類的成員 :(重點)屬性、方法、構(gòu)造器;(熟悉)代碼塊、內(nèi)部類 ? 面向?qū)ο蟮奶卣?:封裝、繼承、多態(tài)、(抽象) ? 其他的使用 :this、super、package、import、static、final、interface、 abstract 等 程序設(shè)計的思路 面向?qū)ο螅?/p>

    2024年02月08日
    瀏覽(17)
  • C++面向?qū)ο筘?. 內(nèi)存分區(qū)模型

    C++面向?qū)ο筘?. 內(nèi)存分區(qū)模型

    Author:AXYZdong 碩士在讀 工科男 有一點思考,有一點想法,有一點理性! 定個小小目標(biāo),努力成為習(xí)慣!在最美的年華遇見更好的自己! CSDN@AXYZdong,CSDN首發(fā),AXYZdong原創(chuàng) 唯一博客更新的地址為: ?? AXYZdong的博客 ?? B站主頁為: AXYZdong的個人主頁 系列文章目錄 C++基礎(chǔ)入門

    2023年04月18日
    瀏覽(19)
  • java中this的內(nèi)存原理以及成員變量和局部變量

    java中this的內(nèi)存原理以及成員變量和局部變量

    區(qū)分局部變量和成員變量 eg: 代表所在方法調(diào)用者的地址值 代表所在方法調(diào)用者的地址值: 此時main方法里面的調(diào)用者是s,s記錄的地址值是001;this的本質(zhì)是方法調(diào)用者的地址值,所以this指向的地址值是001。 this的內(nèi)存原理: 解析: 等號的右邊name出發(fā)了就近原則,表示setN

    2024年02月04日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包