Mengapa Tidak Memulakan Benang Di Konstruktor?

1. Gambaran keseluruhan

Dalam tutorial ringkas ini, kita akan melihat mengapa kita tidak boleh memulakan utas di dalam konstruktor.

Pertama, kami akan memperkenalkan konsep penerbitan secara ringkas di Java dan JVM. Kemudian, kita akan melihat bagaimana konsep ini mempengaruhi cara kita memulakan utas.

2. Penerbitan dan Melarikan diri

Setiap kali kami menyediakan objek untuk kod lain di luar skopnya sekarang, kami pada dasarnya menerbitkan objek tersebut . Sebagai contoh, penerbitan berlaku ketika kita mengembalikan objek, menyimpannya menjadi rujukan umum , atau bahkan meneruskannya ke metode lain.

Apabila kita menerbitkan objek yang tidak seharusnya kita miliki, kita mengatakan bahawa objek tersebut telah melarikan diri .

Ada banyak cara untuk membiarkan rujukan objek melarikan diri, seperti menerbitkan objek sebelum pembinaannya penuh. Sebenarnya, ini adalah salah satu bentuk pelarian yang biasa: apabila rujukan ini melarikan diri semasa pembinaan objek.

Apabila rujukan ini melarikan diri semasa pembinaan, utas lain mungkin melihat objek itu dalam keadaan tidak betul dan tidak dibina sepenuhnya. Ini, seterusnya, boleh menyebabkan komplikasi keselamatan benang yang pelik.

3. Melarikan diri dengan Benang

Salah satu cara yang paling biasa untuk membiarkan rujukan ini melarikan diri adalah dengan memulakan utas dalam konstruktor. Untuk lebih memahami perkara ini, mari kita pertimbangkan satu contoh:

public class LoggerRunnable implements Runnable { public LoggerRunnable() { Thread thread = new Thread(this); // this escapes thread.start(); } @Override public void run() { System.out.println("Started..."); } }

Di sini, kami secara jelas menyampaikan rujukan ini kepada Thread konstruktor. Oleh itu, utas yang baru dimulakan mungkin dapat melihat objek yang dilampirkan sebelum pembinaannya lengkap. Dalam konteks serentak, ini boleh menyebabkan pepijat halus.

Anda juga boleh menyampaikan rujukan ini secara tersirat :

public class ImplicitEscape { public ImplicitEscape() { Thread t = new Thread() { @Override public void run() { System.out.println("Started..."); } }; t.start(); } }

Seperti yang ditunjukkan di atas, kami membuat kelas dalaman tanpa nama yang berasal dari Thread . Oleh kerana kelas dalam mengekalkan rujukan ke kelas penutup mereka, rujukan ini sekali lagi melarikan diri dari pembina.

Tidak ada salahnya membuat Thread di dalam konstruktor. Walau bagaimanapun, sangat tidak digalakkan untuk memulakannya dengan segera , kerana kebanyakan masa, kita berakhir dengan rujukan ini , baik secara eksplisit atau tersirat.

3.1. Alternatif

Daripada memulakan utas di dalam konstruktor, kita boleh menyatakan kaedah khusus untuk senario ini:

public class SafePublication implements Runnable { private final Thread thread; public SafePublication() { thread = new Thread(this); } @Override public void run() { System.out.println("Started..."); } public void start() { thread.start(); } };:

Seperti yang ditunjukkan di atas, kami masih menerbitkan rujukan ini ke Thread. Walau bagaimanapun, kali ini, kami memulakan utas setelah konstruktor kembali:

SafePublication publication = new SafePublication(); publication.start();

Oleh itu, rujukan objek tidak melarikan diri ke utas lain sebelum pembinaannya penuh.

4. Kesimpulan

Dalam tutorial ringkas ini, setelah pengenalan ringkas kepada penerbitan yang selamat, kami melihat mengapa kami tidak boleh memulakan utas di dalam konstruktor.

Maklumat lebih terperinci mengenai penerbitan dan pelarian di Jawa dapat dilihat dalam buku Java Concurrency in Practice.

Seperti biasa, semua contoh boleh didapati di GitHub.