Senkronizasyon ve Kilit


183

java.util.concurrentAPI Lock, kritik kaynağa erişmek için temel olarak denetimi serileştiren as adında bir sınıf sağlar . Bu gibi bir yöntem verir park()ve unpark().

synchronizedAnahtar kelime, yöntem wait()ve notify() notifyAll()yöntemleri kullanabilirsek benzer şeyler yapabiliriz .

Uygulamada bunlardan hangisinin daha iyi olduğunu merak ediyorum ve neden?


Yanıtlar:


178

Bir nesneyi kilitliyorsanız, kullanmayı tercih ederim synchronized

Misal:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

Her try{} finally{}yerde açıkça yapmak zorundasınız .

Senkronize ile yanlış anlaşılması çok net ve imkansız:

synchronized(myObject) {
    doSomethingNifty();
}

Bununla birlikte, Lockböyle temiz bir şekilde elde edemeyeceğiniz ve serbest bırakamayacağınız daha karmaşık şeyler için daha yararlı olabilir. Dürüst olmak gerekirse Lock, ilk etapta çıplak s kullanmaktan kaçınmayı tercih ediyorum ve sadece a CyclicBarrierveya a gibi daha sofistike bir eşzamanlılık kontrolü ile devam ediyorumLinkedBlockingQueue ihtiyaçlarınızı karşılarsa, .

Ben kullanmak için bir neden yoktu hiç wait()ya notify()ama bazı iyi olanlar olabilir.


1
Wait / notify vs park / unpark of LockSupport arasındaki fark nedir? docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
Pacerier

6
İlk başta örnek kilitlerle mantıklıydı, ancak daha sonra bir blok kullanırsanız kilitlerin serbest bırakılmamasıyla bu sorunun önleneceğini fark ettim
William Reed

Ahh ... C ++ 'da RAII modelini takdir eden anlardan biri. std::lock_guard
WhiZTiM

67

Uygulamada bunlardan hangisinin daha iyi olduğunu merak ediyorum ve neden?

Ben Lockve Condition(ve diğer yeni concurrentsınıflar) araç kutusu için sadece daha fazla araç olduğunu buldum . İhtiyacım olan her şeyi eski pençe çekiçimle ( synchronizedanahtar kelime) yapabilirdim, ancak bazı durumlarda kullanmak garipti. Alet kutuma daha fazla alet eklediğimde, bu garip durumların birçoğu çok daha basit hale geldi: bir lastik tokmak, bir top çekiç, bir prybar ve bazı tırnak zımbaları. ancak , eski pençe çekiçim hala kullanım payını görüyor.

Birinin diğerinden gerçekten "daha iyi" olduğunu düşünmüyorum, aksine her biri farklı sorunlara daha uygun. Özetle, basit modeli ve kapsam odaklı doğası synchronizedbeni kodumdaki hatalardan korumaya yardımcı olur, ancak aynı avantajlar bazen daha karmaşık senaryolarda engeldir. Eşzamanlı paketin ele alınmasına yardımcı olmak için oluşturulduğu bu daha karmaşık senaryolar. Ancak bu üst düzey yapıların kullanılması, kodda daha açık ve dikkatli bir yönetim gerektirir.

===

JavaDocLock ve synchronized(bu benim vurgu) arasındaki ayrımı açıklamak iyi bir iş düşünüyorum :

Kilit uygulamaları, senkronize yöntemler ve ifadeler kullanılarak elde edilenden daha kapsamlı kilitleme işlemleri sağlar . Daha esnek yapılandırmaya izin verirler , oldukça farklı özelliklere sahip olabilirler ve birden çok ilişkilendirilmiş Koşul nesnesini destekleyebilirler .

...

Kullanımı senkronize yöntemlerle veya beyana her nesne ile ilişkilendirilmiş örtülü monitör kilidine erişim sağlar ancak güçler kilit edinme ve serbest bırakma bir blok yapılandırılmış bir şekilde gerçekleşmesi için : zaman birden kilitler vardır edinilen onlar ters sırayla serbest bırakılmalıdır ve tüm kilitler, edinildikleri sözcüksel kapsamda serbest bırakılmalıdır .

Senkronize yöntemler ve ifadeler için kapsam belirleme mekanizması , monitör kilitleriyle programlamayı çok kolaylaştırırken ve kilitlerle ilgili birçok yaygın programlama hatalarından kaçınmaya yardımcı olurken, kilitlerle daha esnek bir şekilde çalışmanız gereken durumlar vardır. Örneğin, * * bazı algoritmalar * eşzamanlı olarak erişilen veri yapılarını taramak için "el ele" veya "zincir kilitleme" kullanımını gerektirir : A düğümünün kilidini, sonra B düğümünü alır, sonra A'yı serbest bırakır ve C'yi alırsınız, daha sonra B'yi serbest bırakın ve D'yi edinin vb. Kilit arabiriminin uygulamaları, bir kilidin farklı kapsamlarda edinilmesine ve serbest bırakılmasına izin vererek bu tür tekniklerin kullanılmasını sağlar veherhangi bir sırayla birden fazla kilidin alınmasını ve serbest bırakılmasını sağlar .

Bu artan esneklik ile ek sorumluluk gelir . Blok yapılı kilitleme yokluğu kilitler otomatik olarak serbest bırakılmasını kaldırır senkronize yöntem ve tablolar ile oluşur. Çoğu durumda, aşağıdaki deyim kullanılmalıdır:

...

Ne zaman kilitleme ve kilit açma farklı kapsamlarda meydana , bakım için alınması gereken sağlamak olduğunu tutulduğu kilit sırasında yürütülür tüm kod nihayet-denemede tarafından korunan veya denemek-catch olduğu için kilit salıverilmesini sağlamak gerektiğinde.

Kilit uygulamaları sağlayan ek işlevler bir sağlayarak senkronize yöntem ve tabloların kullanımı üzerinde elde etmek engellenmeyen girişimini kilit (tryLock ()), denemesi kesilebilir kilidi elde lockInterruptibly (() ve denemesi edinilen zaman aşımına uğrayabilecek kilit (tryLock (uzun, TimeUnit)).

...


23

İçeri yardımcı programlar her şeyi elde edebilirsiniz java.util.concurrent gibi düşük seviyeli ilkel ilgisi synchronized, volatileya da bekleme / bildirmek

Bununla birlikte, eşzamanlılık zor ve çoğu insan en azından bazı kısımlarını yanlış anlıyor ve kodlarını yanlış veya verimsiz (veya her ikisi) yapıyor.

Eşzamanlı API, kullanımı daha kolay (ve daha güvenli) olan daha üst düzey bir yaklaşım sağlar. Özetle, synchronized, volatile, wait, notifyartık doğrudan kullanmanıza gerek yok.

Kilit kendisi bu alet çantasından alt düzey tarafındadır sınıf, hatta (kullanabilirsiniz doğrudan ya o kullanmaya gerek olmayabilir Queuesve Semaphore vb falan, çoğu zaman).


2
Sade eski bekle / bildir java.util.concurrent.locks.LockSupport park / unpark'tan daha düşük seviyeli bir ilkel olarak mı düşünülüyor, yoksa tam tersi mi?
Pacerier

@Pacerier: Her ikisinin de düşük seviyeli olduğunu düşünüyorum (yani bir uygulama programcısının doğrudan kullanmaktan kaçınmak isteyeceği bir şey), ancak kesinlikle java.util.concurrency (kilit paketi gibi) alt düzey bölümleri üzerine inşa edilmiştir. Yerli JVM ilkelerinin beklemesi / bildirilmesi (daha da düşük düzeydedir).
Thilo

2
Hayır 3 demek istiyorum: Thread.sleep / interrupt, Object.wait / notify, LockSupport.park / unpark, hangisi en ilkel?
Pacerier

2
@Thilo java.util.concurrent[Genel olarak] dil özelliklerinden ( synchronizedvb.) Daha kolay olan ifadenizi nasıl desteklediğinizden emin değilim . Kullandığınızda java.util.concurrent, lock.lock(); try { ... } finally { lock.unlock() }kod yazmadan önce tamamlama alışkanlığı synchronizededinmeniz gerekirken, bir ile temelde baştan iyi durumdasınız. Sadece bu temelde synchronized(davranışını istediğiniz göz önüne alındığında) daha kolay olduğunu söyleyebilirim java.util.concurrent.locks.Lock. par 4
Iwan Aucamp

1
AtomicXXX sınıflarının davranışını tam olarak eşzamanlılık ilkelleriyle çoğaltabileceğinizi düşünmeyin, çünkü java.util.concurrent'dan önce mevcut olmayan yerel CAS çağrısına güvenirler.
Duncan Armstrong

15

synchronizedVeya neden kullanmak istediğinize dair 4 ana faktör vardır java.util.concurrent.Lock.

Not: Senkronize kilitleme, kendinden kilitleme dediğimde kastettiğim şeydir.

  1. Java 5, ReentrantLocks ile çıktığında, kendinden kilitlemeden çok fark edilir bir verim farkına sahip olduklarını kanıtladılar. Daha hızlı kilitleme mekanizması arıyorsanız ve 1.5 çalıştırıyorsanız jucReentrantLock'u düşünün. Java 6'nın kendinden kilitlenmesi artık karşılaştırılabilir.

  2. jucLock'un kilitleme için farklı mekanizmaları vardır. Kilitlenebilir - kilitleme dişi kesilene kadar kilitlemeye çalışın; zamanlanmış kilit - belirli bir süre kilitlemeye ve başarılı olmazsanız vazgeçmeye çalışın; tryLock - başka bir iş parçacığı kilidi tutarsa ​​kilitlemeyi deneyin. Tüm bunlar basit kilit dışındadır. Kendinden kilitleme sadece basit kilitleme sunar

  3. Stil. Hem 1 hem de 2, kendim de dahil olmak üzere çoğu insanla ilgilendiğiniz kategorilere girmezse, kendinden kilitlemeli semenatiğin okunması daha kolay ve jucLock kilitlemesinden daha az ayrıntılı olur.
  4. Çoklu Koşullar. Kilitlediğiniz bir nesneye yalnızca bir vaka bildirilebilir ve beklenebilir. Lock'un newCondition yöntemi, tek bir Kilidin beklemek veya sinyal vermek için birden fazla nedeni olmasını sağlar. Aslında pratikte bu işlevselliğe ihtiyacım yok, ama ihtiyacı olanlar için güzel bir özellik.

Yorumunuzdaki ayrıntıları beğendim. Ben bir madde işareti daha eklemek istiyorum - ReadWriteLock, yalnızca bazıları nesneye yazmak gerekir birkaç iş parçacığı ile uğraşıyorsanız, yararlı davranış sağlar. Birden çok iş parçacığı aynı anda nesneyi okuyabilir ve yalnızca başka bir iş parçacığı zaten yazıyorsa engellenir.
Sam Goldberg

5

Bert F cevabının üstüne birkaç şey daha eklemek istiyorum .

Locksörtülü monitörlerden ( synchronizedkilitler) daha etkileyici olan daha ince taneli kilit kontrolü için çeşitli yöntemleri destekler

Bir Kilit, paylaşılan bir kaynağa özel erişim sağlar: tek seferde yalnızca bir iş parçacığı kilidi alabilir ve paylaşılan kaynağa tüm erişim, kilidin önce edinilmesini gerektirir. Ancak, bazı kilitler, ReadWriteLock'un okuma kilidi gibi paylaşılan bir kaynağa eşzamanlı erişime izin verebilir.

Belgeler sayfasından Senkronizasyona Karşı Kilitlemenin Avantajları

  1. Senkronize yöntemlerin veya ifadelerin kullanımı, her nesneyle ilişkili örtük monitör kilidine erişim sağlar, ancak tüm kilit alımını ve yayınını blok yapılı bir şekilde gerçekleşmeye zorlar

  2. Kilit uygulamaları, bir engelleme edinme engeli olmayan lock (tryLock())bir girişim, kesilebilen kilidi edinme ( lockInterruptibly()ve mümkün olan kilidi edinme girişimi) sağlayarak senkronize yöntemlerin ve ifadelerin kullanımı üzerinde ek işlevsellik sağlar timeout (tryLock(long, TimeUnit)).

  3. Bir Lock sınıfı, garantili sipariş, evresel olmayan kullanım veya kilitlenme tespiti gibi örtük monitör kilidinden oldukça farklı davranış ve anlamlar da sağlayabilir

ReentrantLock : Anladığım kadarıyla basit terimlerle ReentrantLock, bir nesnenin bir kritik bölümden diğer kritik bölüme tekrar girmesine izin verir. Bir kritik bölüme girmek için zaten kilidiniz olduğundan, geçerli kilidi kullanarak aynı nesne üzerindeki diğer kritik bölümü kullanabilirsiniz.

ReentrantLockbu makaleye göre temel özellikler

  1. Kesintisiz kilitleme yeteneği.
  2. Kilit beklerken zaman aşımı yeteneği.
  3. Adil kilit oluşturma gücü.
  4. Kilit için bekleyen iş parçacığı listesini almak için API.
  5. Engellemeden kilit denemek için esneklik.

ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLockOkuma ve yazma işlemlerinde granüler kilitleme üzerinde daha fazla kontrol elde etmek için kullanabilirsiniz .

Bu üç ReentrantLocks dışında, java 8 bir tane daha Kilit sağlıyor

StampedLock:

Java 8, yukarıdaki örnekte olduğu gibi okuma ve yazma kilitlerini de destekleyen StampedLock adlı yeni bir kilitle birlikte gelir. ReadWriteLock'un aksine, StampedLock'un kilitleme yöntemleri uzun bir değerle temsil edilen bir damga döndürür.

Bu damgaları bir kilidi açmak veya kilidin hala geçerli olup olmadığını kontrol etmek için kullanabilirsiniz. Ayrıca damgalı kilitler, iyimser kilitleme adı verilen başka bir kilit modunu destekler.

Farklı tip ve kilitlerin kullanımı ile ilgili bu makaleye bir göz atın .ReentrantLockStampedLock


4

Temel fark adalettir, başka bir deyişle FIFO tarafından ele alınan talepler midir yoksa engelleme olabilir mi? Yöntem seviyesi senkronizasyonu kilidin adil veya FIFO tahsisini sağlar. kullanma

synchronized(foo) {
}

veya

lock.acquire(); .....lock.release();

adaleti garanti etmez.

Kilit için çok fazla çekişmeniz varsa, yeni isteklerin kilidi aldığı ve eski isteklerin takıldığı yerlerde engelleme ile kolayca karşılaşabilirsiniz. Bir kilit için 200 iş parçacığının kısa sırayla geldiği ve ikincisinin en son işlendiği durumlar gördüm. Bu bazı uygulamalar için uygundur, ancak diğerleri için ölümcül.

Bu konunun tam bir tartışması için Brian Goetz'un "Uygulamada Java Eşzamanlılığı" kitabının 13.3.


5
"Yöntem seviyesi senkronizasyonu kilit adil veya FIFO tahsisi sağlar." => Gerçekten mi? Senkronize bir yöntemin, yöntem içeriğini bir senkronize {} bloğuna sarmaktan farklı olarak adil davrandığını mı söylüyorsunuz? Ben öyle düşünmezdim, ya da o cümleyi yanlış anladım mı?
weiresr

Evet, şaşırtıcı ve sezgisel karşı bu doğru. Goetz'in kitabı en iyi açıklamadır.
Brian Tarbox

@BrianTarbox tarafından sağlanan koda bakarsanız, senkronize edilmiş blok kilitlemek için "bu" dışında bir nesne kullanıyor. Teoride, senkronize edilmiş bir yöntem ile söz konusu yöntemin tüm gövdesini, senkronize edilmiş bir bloğun içine koymak arasında bir fark yoktur;
xburgos

Cevap, alıntıyı içerecek ve "güvence" yi açıklığa kavuşturacak şekilde düzenlenmelidir. Burada belirleyici değil, "istatistiksel garanti" dir.
Nathan Hughes

Üzgünüm, bu cevabı birkaç gün önce yanlışlıkla oyladığımı keşfettim (beceriksiz tıklama). Ne yazık ki, SO şu anda geri döndürmeme izin vermiyor. Umarım daha sonra düzeltebilirim.
Mikko Östlund

3

Brian Goetz'in "Uygulamada Java Eşzamanlılığı" kitabı, bölüm 13.3: "... Varsayılan ReentrantLock gibi, içsel kilitleme belirli bir adalet garantisi sunmaz, ancak çoğu kilitleme uygulamasının istatistiksel adalet garantisi neredeyse tüm durumlar için yeterince iyidir ..."


2

Kilit programcıların hayatını kolaylaştırır. İşte kilitle kolayca gerçekleştirilebilecek birkaç durum.

  1. Bir yöntemle kilitleyin ve başka bir yöntemle kilidi serbest bırakın.
  2. Bununla birlikte, iki farklı kod parçası üzerinde çalışan iki iş parçacığınız varsa, ilk iş parçacığında ikinci iş parçacığında belirli bir kod parçası için bir ön koşul vardır (diğer bazı iş parçacıkları da aynı kod parçası üzerinde çalışır) aynı anda iplik). Paylaşılan bir kilit bu sorunu kolayca çözebilir.
  3. Monitörleri uygulama. Örneğin, put ve get yöntemlerinin diğer birçok iş parçacığından yürütüldüğü basit bir kuyruk. Ancak, aynı anda birden çok put (veya get) yönteminin çalışmasını istemezsiniz, ne put ve get yönteminin aynı anda çalışmasını istersiniz. Özel bir kilit, bunu başarmak için hayatınızı çok daha kolay hale getirir.

Iken, kilit ve koşullar senkronize mekanizma üzerine inşa. Bu nedenle, kilidi kullanarak elde edebileceğiniz aynı işlevselliği kesinlikle elde edebilirsiniz. Ancak, senkronize edilmiş karmaşık senaryoları çözmek hayatınızı zorlaştırabilir ve sizi gerçek sorunu çözmekten alıkoyabilir.


1

Kilit ve senkronize arasındaki büyük fark:

  • kilitleri kullanarak, kilitleri istediğiniz sırayla serbest bırakıp alabilirsiniz.
  • senkronize durumdayken, kilitleri yalnızca alınma sırasına göre serbest bırakabilirsiniz.

0

Kilitleme ve senkronize etme bloğunun her ikisi de aynı amaca hizmet eder, ancak kullanıma bağlıdır. Aşağıdaki kısmı düşünün

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

Yukarıdaki durumda, bir iplik senkronizasyon bloğuna girerse, diğer blok da kilitlenir. Aynı nesne üzerinde birden fazla senkronizasyon bloğu varsa, tüm bloklar kilitlenir. Bu gibi durumlarda, java.util.concurrent.Lock, blokların istenmeyen kilitlenmesini önlemek için kullanılabilir

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.