Java 8 arayüz yöntemlerinde “senkronize edilmesine” izin verilmemesinin nedeni nedir?


210

Java 8'de kolayca yazabilirim:

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

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

Sınıflarda da kullanabileceğim tam senkronizasyon anlambilimini alacağım. Ancak, synchronizeddeğiştirici yöntem bildirimlerinde kullanamıyorum :

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

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

Şimdi, bir haricinde aynı şekilde davranmasına iki arayüzleri iddia edebilir Interface2bir kurar sözleşme üzerinde method1()ve üzerinde method2()olandan biraz daha güçlüdür, Interface1yapar. Tabii ki, defaultuygulamaların somut uygulama durumu hakkında herhangi bir varsayımda bulunmaması gerektiğini veya böyle bir anahtar kelimenin ağırlığını çekmeyeceğini de iddia edebiliriz .

Soru:

JSR-335 uzman grubunun synchronizedarayüz yöntemlerini desteklememeye karar vermesinin nedeni nedir ?


1
Senkronize edilmiş bir uygulama davranışıdır ve derleyici tarafından yapılan son bayt kodu sonucunu değiştirir, böylece bir kodun yanında kullanılabilir. Yöntem bildiriminde bir anlamı yoktur. Senkronize edilmiş soyutlama katmanındaysa, derleyicinin ürettiği şey kafa karıştırıcı olmalıdır.
Martin Strejc

@MartinStrejc: Tutarlılık için bir açıklama olabilir , ancak default synchronizedzorunlu olarak değil static synchronized, ancak ikincisinin tutarlılık nedenlerinden dolayı ihmal edilmiş olabileceğini kabul ediyorum.
Lukas Eder

1
synchronizedDeğiştirici alt sınıflarda geçersiz kılınmış olabilir bu nedenle herhangi bir değer ekleyip eklemediğinden emin değilim , bu nedenle sadece son varsayılan yöntemler olarak bir şey olsaydı önemli olurdu. (Başka sorunuz)
skiwi

@skiwi: Geçersiz kılan argüman yeterli değil. Alt synchronizedsınıflar, süper sınıflarda bildirilen yöntemleri geçersiz kılarak senkronizasyonu etkin bir şekilde kaldırabilir . Ben destekleyen değil sürpriz olmaz synchronizedve desteklemeyen finalbelki çoklu miras (örn miras olsa da, ilgili void x() ve synchronized void x() vb). Ama bu bir tahmin. Varsa, yetkili bir nedeni merak ediyorum.
Lukas Eder

2
>> "Alt sınıflar, süper sınıflarda senkronize olarak bildirilen, senkronizasyonu etkin bir şekilde kaldıran yöntemleri geçersiz kılabilir" ... ancak supertam bir yeniden uygulama ve özel üyelere olası erişim gerektiren çağrılamazlarsa . Btw, bu yöntemlerin "savunucu" olarak adlandırılmasının bir nedeni var - yeni yöntemlerin daha kolay eklenmesine izin vermek için mevcutlar.
bestsss

Yanıtlar:


260

İlk başta, synchronizeddeğiştiriciyi varsayılan yöntemlerde desteklemek isteyeceği açık gibi görünse de , bunun tehlikeli olduğu ve bu yüzden yasaklandığı ortaya çıktı.

Senkronize yöntemler, tüm gövde synchronizedkilit nesnesi alıcı olan bir blok içine alınmış gibi davranan bir yöntem için bir kısayoldur . Bu anlambilimi varsayılan yöntemlere de genişletmek mantıklı görünebilir; sonuçta, onlar da bir alıcı ile örnek yöntemler. ( synchronizedYöntemlerin tamamen sözdizimsel bir optimizasyon olduğunu; gerekli olmadıklarını, karşılık gelen synchronizedbloktan daha kompakt olduklarını unutmayın . Bunun ilk etapta sözdizimsel bir optimizasyon olduğunu ve senkronize edilmiş yöntemlerin makul bir argüman olduğunu çözdüklerinden daha fazla soruna neden oluyorlardı, ancak bu gemi uzun zaman önce yola çıktı.)

Peki, neden tehlikeliler? Senkronizasyon kilitleme ile ilgilidir. Kilitleme, değiştirilebilir duruma paylaşılan erişimi koordine etmekle ilgilidir. Her nesnenin, hangi durum kilitlerinin hangi durum değişkenlerini koruduğunu belirleyen bir senkronizasyon politikası olmalıdır. (Bkz . Uygulamada Java Eşzamanlılığı , bölüm 2.4.)

Birçok nesne senkronizasyon politikası olarak bir nesnenin durumunun kendinden kilitli olduğu Java Monitör Kalıbı'nı (JCiP 4.1) kullanır. Bu model hakkında sihir veya özel bir şey yoktur, ancak uygundur ve synchronizedanahtar kelimelerin yöntemlerde kullanılması bu modeli dolaylı olarak varsayar.

Bu nesnenin senkronizasyon ilkesini belirleyen duruma sahip olan sınıftır. Ancak arayüzler, içinde karıştırıldıkları nesnelerin durumuna sahip değildir. Bu nedenle, bir arabirimde senkronize edilmiş bir yöntem kullanmak belirli bir senkronizasyon ilkesini varsayar, ancak varsaymak için makul bir temeliniz olmadığı varsayılır, bu durumda iyi olabilir senkronizasyon kullanımı hiçbir ek iplik güvenliği sağlamaz (yanlış kilit üzerinde senkronizasyon yapıyor olabilirsiniz). Bu, iş parçacığı güvenliği hakkında bir şey yaptığınıza dair yanlış bir güven duygusu verir ve hiçbir hata iletisi, yanlış eşitleme ilkesini kabul ettiğinizi bildirmez.

Tek bir kaynak dosya için sürekli olarak bir senkronizasyon politikası sürdürmek için yeterince zor; bir alt sınıfın üst sınıfı tarafından tanımlanan eşitleme ilkesine düzgün bir şekilde uymasını sağlamak daha da zordur. Böyle gevşek bağlı sınıflar (bir arabirim ve onu uygulayan muhtemelen birçok sınıf) arasında bunu yapmaya çalışmak neredeyse imkansız ve hataya oldukça eğilimli olacaktır.

Tüm bu iddialar düşünüldüğünde, bu argüman ne için olurdu? Görünüşe göre çoğunlukla arayüzlerin daha çok özellik gibi davranmasıyla ilgili. Bu anlaşılabilir bir arzu olsa da, varsayılan yöntemlerin tasarım merkezi "Özellikler--" değil, arayüz evrimidir. İkisinin tutarlı bir şekilde elde edilebildiği yerlerde, bunu yapmaya gayret gösterdik, ancak birinin diğeri ile çatıştığı yerlerde, birincil tasarım hedefi lehine seçim yapmak zorunda kaldık.


26
JDK 1.1'de, synchronizedyöntem değiştiricinin javadoc çıktısında göründüğünü ve insanları belirtimin bir parçası olduğunu düşünmeye yönlendirdiğine dikkat edin. Bu, JDK 1.2'de giderilmiştir. Genel bir yöntemde görünse bile, synchronizeddeğiştirici sözleşmenin değil uygulamanın bir parçasıdır. ( nativeDeğiştirici için benzer akıl yürütme ve tedavi gerçekleşti .)
Stuart Marks

15
Erken Java programlarındaki yaygın bir hata, yeterince serpmek synchronizedve güvenli bileşenleri etrafa bağlamaktı ve neredeyse güvenli bir programınız vardı. Sorun bu genellikle Tamam çalıştı ama şaşırtıcı ve kırılgan şekillerde kırık oldu. Kilitlemenizin nasıl çalıştığını anlamanın sağlam uygulamalar için bir anahtar olduğunu kabul ediyorum.
Peter Lawrey

10
@BrianGoetz Çok iyi bir sebep. Ama neden synchronized(this) {...}bir defaultyöntemde izin verilir ? (Lukas'ın sorusunda gösterildiği gibi.) Bu, varsayılan yöntemin uygulama sınıfının durumuna da sahip olmasına izin vermiyor mu? Bunu da önlemek istemiyoruz? Bilgisiz geliştiricilerin bunu yaptığı vakaları bulmak için bir FindBugs kuralına ihtiyacımız olacak mı?
Geoffrey De Smet

17
@Geoffrey: Hayır, bunu kısıtlamak için bir neden yoktur (her zaman dikkatli kullanılmalıdır.) Senkronizasyon bloğu yazarın açıkça bir kilit nesnesi seçmesini gerektirir; bu, bu politikanın ne olduğunu biliyorlarsa, başka bir nesnenin senkronizasyon politikasına katılmalarını sağlar. Tehlikeli kısım, 'bu' üzerinde senkronizasyonun (senkronizasyon yöntemlerinin yaptığı şey) aslında anlamlı olduğunu varsaymaktır; bunun daha açık bir karar olması gerekir. Bununla birlikte, arayüz yöntemlerindeki senkronizasyon bloklarının oldukça nadir olmasını bekliyorum.
Brian Goetz

6
@GeoffreyDeSmet: Aynı nedenden dolayı örneğin synchronized(vector). Güvende olmak istediğinizde, thiskilitlemek için asla ortak bir nesne ( kendisi gibi ) kullanmamalısınız.
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();
}
}

sonuç:

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

(üst sınıfı örnek olarak kullandığım için üzgünüm)

Sonuç olarak, üst sınıf kilidinin her alt sınıfa ait olduğunu biliyoruz, SonSync1 ve SonSync2 nesnesi farklı nesne kilidine sahip. her kilit bağımsızlıktır. bu durumda, ben bir üst sınıf veya ortak bir arabirimde bir senkronize kullanarak tehlikeli olmadığını düşünüyorum. kimse bu konuda daha fazla açıklayabilir?

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.