การใช้งาน WeakReference ที่หายาก?

Nov 07 2020

ฉันมีคลาสที่มีการเตรียมใช้งานอินสแตนซ์และใช้งานโดยการใช้แฟลตฟอร์ม

class MyAttributeConverter implements AttributeConverter<XX, YY> {

    public YY convertToDatabaseColumn(XX attribute) { return null; }

    public XX convertToEntityAttribute(YY dbData) { return null; }
}

ไม่มีอะไรผิดปกติและฉันคิดว่าฉันต้องเพิ่มวิธีการคงที่เพื่อใช้เป็นข้อมูลอ้างอิงวิธีการ

    private static MyAttributeConverter instance;

    // just a lazy-initialization;
    // no synchronization is required;
    // multiple instantiation is not a problem;
    private static MyAttributeConverter instance() {
        if (instance == null) {
            instance = new MyAttributeConverter();
        }
        return instance;
    }

    // do as MyAttributeConverter::toDatabaseColumn(xx)

    public static YY toDatabaseColumn(XX attribute) {
        return instance().convertToDatabaseColumn(attribute);
    }

    public static XX toEntityAttribute(YY dbData) {
        return instance().convertToEntityAttribute(attribute);
    }

ดูเหมือนจะไม่มีอะไรผิดปกติ (ฉันเชื่อ) และฉันไม่ชอบที่instanceอยู่กับชั้นเรียนและนั่นคือเหตุผลที่ฉันพยายามทำสิ่งนี้

    private static WeakReference<MyAttributeConverter> reference;

    public static <R> R applyInstance(Function<? super MyAttributeConverter, ? extends R> function) {
        MyAttributeConverter referent;
        if (reference == null) {
            referent = new MyAttributeConverter();
            refernce = new WeakReference<>(referent);
            return applyInstance(function);
        }
        referent = reference.get();
        if (referent == null) {
            referent = new MyAttributeConverter();
            refernce = new WeakReference<>(referent);
            return applyInstance(function);
        }
        return function.apply(referent); // @@?
    }

โดยพื้นฐานแล้วฉันไม่รู้ด้วยซ้ำว่าจะทดสอบโค้ดนี้อย่างไร และฉันขอโทษสำหรับคำถามของฉันซึ่งแต่ละคำถามอาจคลุมเครือ

  • นี่เป็นแนวทาง (ถูก / ผิด) หรือไม่?
  • มีโอกาสใด ๆ ที่reference.get()ภายในfunction.applyสำนวนอาจจะnull?
  • มีโอกาสที่อาจเกิดปัญหาบางอย่างเช่นหน่วยความจำรั่วหรือไม่?
  • ฉันควรพึ่งพาSoftReferenceมากกว่าWeakReference?

ขอขอบคุณ.

คำตอบ

5 Holger Nov 24 2020 at 20:32

โปรดทราบว่าวิธีการเช่น

// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
    if (instance == null) {
        instance = new MyAttributeConverter();
    }
    return instance;
}

ไม่ปลอดภัยเธรดเนื่องจากมีการอ่านสองinstanceฟิลด์ แต่ละคนอาจรับรู้การอัปเดตที่ทำโดยเธรดอื่นหรือไม่ นี่ก็หมายความว่าอ่านครั้งแรกในinstance == nullอาจจะรับรู้เป็นค่าใหม่เขียนโดยหัวข้ออื่นในขณะที่สองอาจประเมินค่าก่อนหน้านี้คือreturn instance; nullดังนั้นวิธีนี้อาจส่งคืนnullเมื่อมีเธรดมากกว่าหนึ่งเธรดกำลังดำเนินการพร้อมกัน นี่เป็นกรณีมุมที่หายาก แต่วิธีนี้ไม่ปลอดภัย คุณต้องมีตัวแปรภายในเพื่อให้แน่ใจว่าการทดสอบและคำสั่งส่งคืนใช้ค่าเดียวกัน

// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
    MyAttributeConverter current = instance;
    if (current == null) {
        instance = current = new MyAttributeConverter();
    }
    return current;
}

สิ่งนี้จะปลอดภัยMyAttributeConverterก็ต่อเมื่อไม่เปลี่ยนรูปโดยใช้finalช่องเท่านั้น มิฉะนั้นเธรดอาจส่งคืนอินสแตนซ์ที่สร้างโดยเธรดอื่นในสถานะที่สร้างขึ้นอย่างไม่สมบูรณ์

คุณสามารถใช้วิธีง่ายๆในการทำให้ปลอดภัยโดยไม่มีข้อ จำกัด เหล่านี้:

private static final MyAttributeConverter instance = new MyAttributeConverter();

private static MyAttributeConverter instance() {
    return instance;
}

นี้ยังคงเป็นคนขี้เกียจเป็นเพียงการเริ่มต้นเรียนที่เกิดขึ้นในหนึ่งของทริกเกอร์ที่ระบุinstance()คือการภาวนาแรกของวิธีการ


การใช้งานของคุณWeakReferenceอาจมีปัญหาเดียวกัน นอกจากนี้ยังไม่ชัดเจนว่าเหตุใดคุณจึงหันไปใช้วิธีการเรียกใช้ซ้ำสองจุดที่คุณมีอาร์กิวเมนต์ที่ต้องการในตัวแปรท้องถิ่นอยู่แล้ว

การใช้งานที่ถูกต้องสามารถทำได้ง่ายกว่ามาก:

private static WeakReference<MyAttributeConverter> reference;

public static <R> R applyInstance(
    Function<? super MyAttributeConverter, ? extends R> function) {

    WeakReference<MyAttributeConverter> r = reference;
    MyAttributeConverter referent = r != null? r.get(): null;      
    if (referent == null) {
        referent = new MyAttributeConverter();
        reference = new WeakReference<>(referent);
    }
    return function.apply(referent);
}

แต่ก่อนที่คุณจะใช้งานคุณควรพิจารณาใหม่ว่ารหัสที่ซับซ้อนนั้นคุ้มค่ากับความพยายามหรือไม่ ความจริงที่ว่าคุณยอมรับความจำเป็นในการสร้างวัตถุขึ้นใหม่เมื่อมีการรวบรวมขยะแม้กระทั่งการสร้างหลายอินสแตนซ์ในการเรียกใช้พร้อมกันขอแนะนำให้คุณทราบว่าการก่อสร้างจะมีราคาถูก เมื่อการก่อสร้างมีราคาถูกคุณอาจไม่จำเป็นต้องแคชอินสแตนซ์ของมันเลย

เพียงแค่พิจารณา

public static <R> R applyInstance(
    Function<? super MyAttributeConverter, ? extends R> function) {

    return function.apply(new MyAttributeConverter());
}

อย่างน้อยก็คุ้มค่าที่จะลองวัดประสิทธิภาพของแอปพลิเคชันและเปรียบเทียบกับแนวทางอื่น ๆ

ในทางกลับกันดูเหมือนว่าอินสแตนซ์ใช้หน่วยความจำจำนวนมากหรือไม่ถือทรัพยากรที่ไม่ใช่หน่วยความจำ มิฉะนั้นคุณกังวลมากขึ้นเกี่ยวกับความเป็นไปได้ที่จะมีอินสแตนซ์จำนวนมากบินไปมา ดังนั้นตัวแปรอื่น ๆ ที่ควรลองและเปรียบเทียบคือตัวแปรที่แสดงด้านบนโดยใช้static finalฟิลด์ที่มีการเริ่มต้นคลาสขี้เกียจและไม่มีโอกาสที่จะเก็บขยะจากวัตถุขนาดเล็กนั้น


การชี้แจงครั้งสุดท้าย คุณถาม

มีโอกาสใด ๆ ที่reference.get()ภายในfunction.applyสำนวนอาจจะnull?

เนื่องจากไม่มีreference.get()การร้องขอในการประเมินfunction.applyจึงไม่มีโอกาสที่การร้องขอดังกล่าวจะประเมินถึงnullจุดนี้ ฟังก์ชันนี้ได้รับการอ้างอิงที่ชัดเจนและเนื่องจากรหัสการโทรทำให้มั่นใจได้ว่าการอ้างอิงที่แข็งแกร่งนี้ไม่ใช่การอ้างอิงnullจึงจะไม่กลายเป็นnullระหว่างการเรียกใช้applyวิธี

โดยทั่วไปตัวรวบรวมขยะจะไม่เปลี่ยนแปลงสถานะของแอปพลิเคชันในลักษณะที่โค้ดที่ใช้การอ้างอิงที่ชัดเจนจะสังเกตเห็นความแตกต่าง (ปล่อยให้หน่วยความจำว่างมากกว่า)

แต่เนื่องจากคุณถามเฉพาะเกี่ยวกับreference.get()การเก็บขยะอาจจะเก็บรวบรวมวัตถุหลังการใช้งานเป็นครั้งสุดท้าย , โดยไม่คำนึงถึงวิธีการหรือประหารชีวิตขอบเขตท้องถิ่น ดังนั้นผู้อ้างอิงสามารถรวบรวมได้ระหว่างการดำเนินการของapplyเมธอดเมื่อเมธอดนี้ไม่ใช้อ็อบเจกต์อีกต่อไป การเพิ่มประสิทธิภาพรันไทม์อาจทำให้สิ่งนี้เกิดขึ้นเร็วกว่าที่คุณคาดเดาได้โดยดูที่ซอร์สโค้ดเนื่องจากสิ่งที่อาจดูเหมือนการใช้อ็อบเจ็กต์ (เช่นการอ่านฟิลด์) อาจไม่ใช้อ็อบเจ็กต์ในรันไทม์ (เช่นเนื่องจากค่านั้นถูกเก็บไว้แล้วใน a การลงทะเบียน CPU ทำให้ไม่จำเป็นต้องเข้าถึงหน่วยความจำของวัตถุ) ตามที่กล่าวไว้ทั้งหมดนี้ไม่มีการปรับเปลี่ยนพฤติกรรมของวิธีการ

ดังนั้นสมมุติฐานreference.get()ในระหว่างการดำเนินการของapplyวิธีการโดยหลักการสามารถประเมินได้nullแต่ไม่มีเหตุผลที่น่ากังวลดังที่กล่าวไว้พฤติกรรมของapplyวิธีการไม่เปลี่ยนแปลง JVM จะเก็บหน่วยความจำของออบเจ็กต์ไว้ตราบเท่าที่จำเป็นเพื่อให้แน่ใจว่าวิธีการดำเนินการนี้ถูกต้อง

แต่คำอธิบายนั้นก็เพื่อความสมบูรณ์ ดังที่กล่าวไว้ว่าคุณไม่ควรใช้การอ้างอิงที่อ่อนแอหรืออ่อนสำหรับวัตถุที่ไม่ถือทรัพยากรราคาแพง