Apa alasan mengapa “sinkronisasi” tidak diizinkan dalam metode antarmuka Java 8?

210

Di Java 8, saya dapat dengan mudah menulis:

interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }

    static void method2() {
        synchronized (Interface1.class) {
            // Something
        }
    }
}

Saya akan mendapatkan semantik sinkronisasi penuh yang dapat saya gunakan juga di kelas. Namun saya tidak bisa menggunakan synchronizedpengubah pada deklarasi metode:

interface Interface2 {
    default synchronized void method1() {
        //  ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }

    static synchronized void method2() {
        // ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
}

Sekarang, orang dapat berargumen bahwa kedua antarmuka berperilaku dengan cara yang sama kecuali yang Interface2membuat kontrak terus method1()-menerus method2(), yang sedikit lebih kuat daripada apa yang Interface1dilakukan. Tentu saja, kami mungkin juga berpendapat bahwa defaultimplementasi tidak boleh membuat asumsi tentang keadaan implementasi konkret, atau bahwa kata kunci seperti itu tidak akan menarik.

Pertanyaan:

Apa alasan mengapa kelompok ahli JSR-335 memutuskan untuk tidak mendukung synchronizedmetode antarmuka?

Lukas Eder
sumber
1
Disinkronkan adalah perilaku implementasi dan itu mengubah hasil kode byte akhir yang dibuat oleh kompiler sehingga dapat digunakan di samping kode. Tidak ada artinya dalam deklarasi metode. Harus membingungkan apa yang dihasilkan kompiler jika disinkronkan pada lapisan abstraksi.
Martin Strejc
@ MartinStrejc: Itu mungkin penjelasan untuk menghilangkan default synchronized, tetapi belum tentu untuk static synchronized, meskipun saya akan menerima bahwa yang terakhir mungkin telah dihilangkan karena alasan konsistensi.
Lukas Eder
1
Saya tidak yakin apakah pertanyaan ini menambah nilai karena synchronizedpengubahnya mungkin ditimpa dalam subkelas, maka itu hanya masalah jika ada sesuatu sebagai metode default akhir. (Pertanyaan Anda yang lain)
skiwi
@skiwi: Argumen utama tidak cukup. Subkelas dapat mengabaikan metode yang dideklarasikan synchronizeddi kelas super, secara efektif menghapus sinkronisasi. Saya tidak akan terkejut bahwa tidak mendukung synchronizeddan tidak mendukung finalterkait, mungkin karena beberapa pewarisan (misalnya mewarisi void x() dan synchronized void x() , dll.). Tapi itu spekulasi. Saya ingin tahu tentang alasan otoritatif, jika ada.
Lukas Eder
2
>> "Subclass dapat mengganti metode yang dinyatakan tersinkronisasi di kelas super, secara efektif menghapus sinkronisasi" ... hanya jika mereka tidak memanggil superyang membutuhkan implementasi ulang penuh dan kemungkinan akses ke anggota pribadi. Btw, ada alasan metode-metode itu disebut "pembela" - mereka hadir untuk memudahkan menambahkan metode baru.
bestsss

Jawaban:

260

Meskipun pada awalnya mungkin tampak jelas bahwa seseorang ingin mendukung synchronizedpengubah pada metode default, ternyata hal itu akan berbahaya, dan itu dilarang.

Metode yang disinkronkan adalah singkatan untuk metode yang berperilaku seolah-olah seluruh tubuh tertutup dalam synchronizedblok yang objek kuncinya adalah penerima. Tampaknya masuk akal untuk memperluas semantik ini ke metode standar juga; setelah semua, mereka adalah metode instan dengan penerima juga. (Perhatikan bahwa synchronizedmetode sepenuhnya merupakan optimasi sintaksis; mereka tidak diperlukan, mereka hanya lebih kompak daripada synchronizedblok yang sesuai . Ada argumen yang masuk akal untuk dibuat bahwa ini adalah optimasi sintaksis prematur di tempat pertama, dan metode yang disinkronkan menyebabkan lebih banyak masalah daripada yang mereka pecahkan, tetapi kapal itu berlayar sejak lama.)

Jadi, mengapa mereka berbahaya? Sinkronisasi adalah tentang penguncian. Mengunci adalah tentang mengoordinasikan akses bersama untuk keadaan yang bisa berubah. Setiap objek harus memiliki kebijakan sinkronisasi yang menentukan kunci mana yang melindungi variabel keadaan. (Lihat Java Concurrency in Practice , bagian 2.4.)

Banyak objek yang digunakan sebagai kebijakan sinkronisasi Java Monitor Pattern (JCiP 4.1), di mana keadaan objek dijaga oleh kunci intrinsiknya. Tidak ada yang ajaib atau istimewa tentang pola ini, tetapi itu nyaman, dan penggunaan synchronizedkata kunci pada metode secara implisit mengasumsikan pola ini.

Kelaslah yang memiliki status yang dapat menentukan kebijakan sinkronisasi objek itu. Tetapi antarmuka tidak memiliki keadaan objek yang dicampur. Jadi, menggunakan metode yang disinkronkan dalam antarmuka mengasumsikan kebijakan sinkronisasi tertentu, tetapi yang Anda tidak memiliki dasar yang masuk akal untuk mengasumsikan, sehingga mungkin terjadi bahwa penggunaan sinkronisasi tidak memberikan keselamatan benang tambahan apa pun (Anda mungkin menyinkronkan kunci yang salah). Ini akan memberi Anda kepercayaan diri yang salah bahwa Anda telah melakukan sesuatu tentang keamanan utas, dan tidak ada pesan kesalahan yang memberi tahu Anda bahwa Anda menganggap kebijakan sinkronisasi salah.

Sudah cukup sulit untuk secara konsisten mempertahankan kebijakan sinkronisasi untuk satu file sumber; bahkan lebih sulit untuk memastikan bahwa subkelas dengan benar mematuhi kebijakan sinkronisasi yang ditentukan oleh superclass-nya. Mencoba melakukannya antara kelas-kelas yang digabungkan secara longgar (sebuah antarmuka dan kemungkinan banyak kelas yang mengimplementasikannya) akan hampir mustahil dan sangat rawan kesalahan.

Mengingat semua argumen itu bertentangan, untuk apa argumen itu? Sepertinya mereka kebanyakan tentang membuat antarmuka berperilaku lebih seperti ciri-ciri. Meskipun ini adalah keinginan yang dapat dimengerti, pusat desain untuk metode standar adalah evolusi antarmuka, bukan "Ciri -". Di mana keduanya dapat dicapai secara konsisten, kami berusaha untuk melakukannya, tetapi di mana satu bertentangan dengan yang lain, kami harus memilih yang mendukung tujuan desain utama.

Brian Goetz
sumber
26
Perhatikan juga bahwa di JDK 1.1, synchronizedpengubah metode muncul di keluaran javadoc, menyesatkan orang untuk berpikir bahwa itu adalah bagian dari spesifikasi. Ini diperbaiki di JDK 1.2. Bahkan jika itu muncul pada metode publik, synchronizedpengubah adalah bagian dari implementasi, bukan kontrak. (Penalaran dan perawatan yang serupa juga terjadi pada nativemodifikator.)
Stuart Marks
15
Kesalahan umum dalam program Java awal adalah memercikkan cukup synchronizeddan mengaitkan komponen yang aman di sekitar dan Anda memiliki program yang hampir aman. Masalahnya adalah ini biasanya bekerja dengan baik tetapi rusak dengan cara yang mengejutkan dan rapuh. Saya setuju bahwa memahami cara kerja penguncian Anda adalah kunci untuk aplikasi yang kuat.
Peter Lawrey
10
@BrianGoetz Alasan yang sangat bagus. Tetapi mengapa synchronized(this) {...}diijinkan dalam suatu defaultmetode? (Seperti yang ditunjukkan dalam pertanyaan Lukas.) Bukankah itu memungkinkan metode default untuk memiliki status kelas implementasi juga? Bukankah kita juga ingin mencegahnya? Apakah kita memerlukan aturan FindBugs untuk menemukan kasus-kasus yang dilakukan pengembang yang tidak mendapat informasi?
Geoffrey De Smet
17
@ Geoffrey: Tidak, tidak ada alasan untuk membatasi ini (meskipun harus selalu digunakan dengan hati-hati.) Blok sinkronisasi mengharuskan penulis untuk secara eksplisit memilih objek kunci; ini memungkinkan mereka untuk berpartisipasi dalam kebijakan sinkronisasi beberapa objek lain, jika mereka tahu apa kebijakan itu. Bagian yang berbahaya adalah mengasumsikan bahwa sinkronisasi pada 'ini' (yang dilakukan metode sinkronisasi) sebenarnya bermakna; ini perlu keputusan yang lebih eksplisit. Yang mengatakan, saya berharap blok sinkronisasi dalam metode antarmuka menjadi sangat langka.
Brian Goetz
6
@ GeoffreyDeSmet: Untuk alasan yang sama Anda dapat melakukan mis synchronized(vector). Jika Anda ingin aman, Anda tidak boleh menggunakan objek publik (seperti thisitu sendiri) untuk mengunci.
Yogu
0
public class ParentSync {

public synchronized void parentStart() {
    System.out.println("I am " + this.getClass() + " . parentStarting. now:" + nowStr());
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("I am " + this.getClass() + " . parentFinished. now" + nowStr());
}

private String nowStr() {
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}


public class SonSync1 extends ParentSync {
public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SonSync2 extends ParentSync {

public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SyncTest {
public static void main(String[] args) throws Exception {

    new Thread(() -> {
        new SonSync1().sonStart();
    }).start();

    new Thread(() -> {
        new SonSync2().sonStart();
    }).start();

    System.in.read();
}
}

hasil:

I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonFinished
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonFinished

(maaf karena menggunakan kelas induk sebagai contoh)

dari hasilnya, kita bisa tahu bahwa kunci kelas induk dimiliki oleh setiap sub kelas, objek SonSync1 dan SonSync2 memiliki kunci objek yang berbeda. setiap kunci adalah independensi. jadi dalam hal ini, saya pikir itu tidak berbahaya menggunakan disinkronkan di kelas induk atau antarmuka umum. adakah yang bisa menjelaskan lebih lanjut tentang ini?

zhenke zhu
sumber