Gesaan dan skop
Penemu duga mungkin bertanya: “Apakah masalah yang diselesaikan oleh ScopedValue dalam Java 25? Bandingkannya dengan ThreadLocal dan terangkan penggunaan yang betul dengan kolam benang (thread pools) atau benang maya (virtual threads).”
JEP 506 memuktamadkan Scoped Values dalam JDK 25. Ia membolehkan pemanggil berkongsi data tak boleh ubah (immutable) dengan pemanggil dalaman dan benang anak di dalam skop leksikal, mengurangkan keperluan untuk menghantar konteks melalui setiap parameter kaedah. Soalan ini menguji jangka hayat, keterlihatan dan sempadan konkurensi; ia tidak dijawab dengan hanya memanggil ScopedValue sebagai “ThreadLocal baharu.”
Perkara yang diuji oleh penemu duga
- Sama ada anda memahami ScopedValue sebagai parameter tersirat yang tak boleh ubah dan dikawal skop.
- Sama ada anda boleh menerangkan pengikatan dan pemulihan
where(...).run(...)ataucall(...). - Sama ada anda mengenali perbezaan daripada ThreadLocal yang boleh ubah (mutable), penggunaan semula benang terkumpul (pooled-thread), dan pewarisan benang maya.
- Sama ada anda mempertimbangkan kawalan akses kunci, bilangan pengikatan, penyebaran pengecualian (exception propagation) dan pembatalan.
- Sama ada anda tahu bila parameter eksplisit atau ThreadLocal kekal sesuai.
Soalan penjelasan
- Adakah nilai yang dikongsi merupakan ID permintaan, prinsipal, penyewa (tenant) atau keadaan transaksi yang boleh ubah?
- Adakah ia mesti baca sahaja (read-only), dan patutkah tugas anak mewarisinya?
- Adakah pelaksanaan dilakukan pada benang platform, benang maya, konkurensi berstruktur atau kolam sedia ada?
- Bolehkah kod membaca kunci di luar skopnya atau mendedahkan Carrier kepada kod yang tidak dipercayai?
- Adakah rangka kerja bergantung pada pembersihan ThreadLocal, mutasi atau integrasi MDC?
Jawapan 30 saat
Anda boleh katakan:
ScopedValue ialah mekanisme konteks Java 25 yang tak boleh ubah dan berskop leksikal. Pemanggil mencipta pengikatan denganScopedValue.where(key, value).run(...)ataucall(...); ia dipulihkan apabila skop ditamatkan, dan hanya kod yang memegang kunci boleh membacanya. Berbanding dengan ThreadLocal, ia mengurangkan pembersihan benang terkumpul dan kebocoran keadaan boleh ubah, menjadikannya berguna untuk konteks permintaan, benang maya dan konkurensi berstruktur. Ia tidak menggantikan ThreadLocal yang mesti dikemas kini langkah demi langkah, dan sesuatu nilai tidak boleh terkeluar daripada skopnya. Saya akan menguji pewarisan, pengecualian, pembatalan dan keterlihatan kunci pada JDK sasaran.
Penaakulan langkah demi langkah
Fahami model pengikatan
Nilai boleh dibaca semasa julat pelaksanaan dinamik, manakala pengikatan dibuat secara eksplisit oleh struktur kod:
static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
ScopedValue.where(REQUEST_ID, "req-42").run(() -> {
audit("start");
handleRequest();
});
static void audit(String message) {
logger.info("{} {}", REQUEST_ID.orElse("missing"), message);
}audit tidak memerlukan parameter ID permintaan, tetapi ia membaca nilai yang bermakna hanya di dalam skop pengikatan. Sebaik sahaja skop ditamatkan, pengikatan tidak boleh mencemari benang pemanggil.
Bandingkan kebolehubahan (mutability) ThreadLocal
ThreadLocal memberikan setiap benang nilai yang boleh ubah; penggunaan semula benang terkumpul memerlukan pembersihan atau permintaan seterusnya mungkin melihat konteks lapuk. ScopedValue direka untuk perkongsian tak boleh ubah, dengan pengikatan bersarang yang membayangi (shadow) nilai luar buat sementara waktu dan memulihkannya selepas itu. Ia mengurangkan tanggungjawab pembersihan tetapi tidak membawa mesin keadaan yang mesti di-set berulang kali oleh kod dalaman.
Mengendalikan tugas anak dan benang maya
JEP 506 menyasarkan kos perkongsian yang boleh diramal dengan benang maya dan konkurensi berstruktur. Sama ada pewarisan berlaku, bila ia ditangkap, dan bagaimana pelaksana (executor) berkelakuan mesti disahkan terhadap JDK sasaran dan kontrak API; jangan memindahkan andaian daripada kolam benang biasa. Objek yang dikongsi harus kekal tak boleh ubah supaya rujukan tidak dapat memintas sempadan konteks.
Mereka bentuk kunci, pengecualian dan keserasian
Jadikan kunci sebagai objek private static final atau sebahagian daripada API yang dikawal. Jangan anggap kunci yang dikongsi secara global sebagai sempadan keselamatan. Gunakan orElse atau pengecualian eksplisit untuk nilai yang hilang; dalam Java 25, orElse tidak lagi menerima null. Pengecualian yang meninggalkan run atau call tetap menamatkan skop dan memulihkan pengikatan luar. Untuk JDK yang lebih lama, kekalkan parameter eksplisit atau pelaksanaan ThreadLocal di sebalik laluan keserasian yang diuji.
Model jawapan berkualiti tinggi
Saya menganggap ScopedValue sebagai parameter tersirat yang tak boleh ubah, bukan pengganti boleh ubah untuk ThreadLocal. Titik masuk permintaan mencipta skop berstruktur denganwhere(key, value).runataucall; kaedah dalaman membaca ID permintaan, penyewa atau prinsipal melalui kunci, dan pengikatan dipulihkan semasa keluar. Ia sesuai dengan konteks baca sahaja dalam benang maya dan konkurensi berstruktur serta mengelakkan kebocoran pembersihan benang terkumpul. ThreadLocal kekal berguna untuk keadaan yang mesti dikemas kini langkah demi langkah, dengan pembersihan dalam finally. Saya akan mengehadkan keterlihatan kunci, menguji pewarisan tugas anak, pengecualian, pembatalan, pembayangan bersarang dan bacaan di luar skop, serta mengekalkan pelaksanaan eksplisit atau serasi untuk JDK yang lebih lama.
Kesilapan biasa
- Memanggil ScopedValue sebagai ThreadLocal boleh ubah dengan pembersihan automatik.
- Menyimpan Carrier atau nilai di luar skop, atau membaca kunci selepas jangka hayatnya.
- Mengabaikan penggunaan semula benang terkumpul, pewarisan tugas anak dan perbezaan benang maya.
- Menyimpan objek boleh ubah sambil mendakwa keseluruhan konteks adalah tak boleh ubah.
- Berkongsi kunci merentas modul sewenang-wenangnya dan tersilap menganggap kawalan akses sebagai penyulitan.
- Terlupa peraturan non-null
orElseJava 25 atau meninggalkan laluan untuk JDK yang lebih lama.
Soalan susulan dan jawapan
1. Bolehkah ScopedValue menggantikan setiap ThreadLocal?
Tidak. Ia sesuai dengan konteks baca sahaja yang terikat pada skop. Keadaan yang mesti dikemas kini melalui rantaian panggilan masih boleh menggunakan ThreadLocal atau parameter eksplisit, dengan tanggungjawab pembersihan yang diperlukan.
2. Apakah yang berlaku dengan pengikatan bersarang?
Skop dalaman boleh mengikat nilai baharu buat sementara waktu untuk kunci yang sama; keluar daripadanya akan memulihkan nilai luar. Laluan pengecualian juga menamatkan skop, jadi nilai dalaman tidak bocor ke luar.
3. Bagaimana anda menguji pewarisan serentak?
Rangkumi benang platform, benang maya, tugas berstruktur dan penggunaan semula benang terkumpul. Periksa nilai yang dilihat oleh tugas anak, pembersihan selepas pembatalan, pembayangan bersarang dan bacaan di luar skop. Tetapkan versi JDK dan konfigurasi pelaksana dalam matriks ujian.