İşte bunun nasıl yapıldığına dair bir açıklama ve örnek. Net olmayan parçalar varsa bana bildirin.
Kaynak ile Gist
Evrensel
Başlatma:
İplik indeksleri atomik olarak artırılır. Bu bir AtomicInteger
adlandırılmış kullanılarak yönetilir nextIndex
. Bu dizinler, bir ThreadLocal
sonraki dizini nextIndex
alıp arttırarak kendisini başlatan bir örnek aracılığıyla iş parçacıklarına atanır . Bu, her iş parçacığının dizini ilk kez alındığında gerçekleşir. ThreadLocal
Bu iş parçacığının son oluşturduğu diziyi izlemek için A oluşturulur. 0 olarak başlatıldı. Sıralı fabrika nesnesi başvurusu aktarılır ve saklanır. AtomicReferenceArray
Boyut olarak iki örnek oluşturulur n
. Kuyruk nesnesi, Sequential
fabrika tarafından sağlanan başlangıç durumuyla başlatılan her bir referansa atanır . n
izin verilen maksimum iş parçacığı sayısıdır. Bu dizilerdeki her eleman karşılık gelen evre dizinine 'aittir'.
Uygulama yöntemi:
İlginç işi yapan yöntem budur. Aşağıdakileri yapar:
- Bu çağrı için yeni bir düğüm oluştur: benim
- Bu yeni düğümü, geçerli iş parçacığının dizinindeki anons dizisinde ayarla
Sonra sıralama döngüsü başlar. Mevcut çağrı dizilenene kadar devam edecektir:
- bu iş parçacığı tarafından oluşturulan son düğümün sırasını kullanarak anons dizisindeki bir düğümü bulun. Bu konuyla ilgili daha sonra.
- 2. adımda bir düğüm bulunursa, henüz dizilenmez, onunla devam edin, aksi takdirde sadece geçerli çağrıya odaklanın. Bu, çağrı başına yalnızca bir düğüme yardımcı olmaya çalışacaktır.
- 3. adımda seçilen düğüm ne olursa olsun, son sıralı düğümden sonra sıralamaya çalışın (diğer dişler karışabilir.) Başarı ne olursa olsun, geçerli iş parçacığı baş referansını döndürülen diziye ayarlayın
decideNext()
Yukarıda açıklanan iç içe döngünün anahtarı decideNext()
yöntemdir. Bunu anlamak için Node sınıfına bakmamız gerekiyor.
Düğüm sınıfı
Bu sınıf, çift bağlantılı bir listedeki düğümleri belirtir. Bu sınıfta çok fazla eylem yok. Yöntemlerin çoğu, oldukça açıklayıcı olması gereken basit geri alma yöntemleridir.
kuyruk yöntemi
bu, 0 dizisiyle özel bir düğüm örneği döndürür. Bir çağırma yerine geçene kadar yer tutucu görevi görür.
Özellikler ve başlatma
seq
: -1 olarak başlatılmış sıra numarası (sıralanmamış anlamına gelir)
invocation
: çağrılmasının değeri apply()
. İnşaat üzerine ayarlayın.
next
: AtomicReference
ileri bağlantı için. bir kez atandığında, bu asla değişmeyecek
previous
: AtomicReference
sıralama sırasında atanan ve tarafından silinen geriye doğru bağlantı içintruncate()
Sonrakine Karar Ver
Bu yöntem, önemsiz mantığı olan Düğümde yalnızca bir tanesidir. Özetle, bir düğüm, bağlantılı listedeki bir sonraki düğüm olmaya aday olarak sunulur. compareAndSet()
Yöntem 's referans boş olup olmadığını kontrol ve eğer öyleyse, adayın başvurusunu ayarlayacaktır. Referans zaten ayarlanmışsa, hiçbir şey yapmaz. Bu işlem atomiktir, bu yüzden aynı anda iki aday sunulursa, sadece bir aday seçilir. Bu, yalnızca bir düğümün bir sonraki düğümün seçileceğini garanti eder. Aday düğüm seçilirse, sırası bir sonraki değere ayarlanır ve önceki bağlantısı bu düğüme ayarlanır.
Evrensel sınıfa uygulama yöntemine geri dönme ...
Düğümümüzden veya decideNext()
diziden bir düğümle son sıralı düğümü (işaretlendiğinde) çağırdıktan sonra, announce
iki olası durum vardır: 1. Düğüm başarıyla dizildi 2. Başka bir iş parçacığı bu iş parçacığını önceden boşalttı.
Bir sonraki adım, bu çağırma için düğümün oluşturulup oluşturulmadığını kontrol etmektir. Bu, bu iş parçacığı başarıyla dizildi veya diziden diğer bir iş parçacığı announce
diziden aldı ve bizim için dizildi çünkü olabilir. Sıralanmamışsa, işlem tekrarlanır. Aksi takdirde çağrı, bu iş parçacığının dizinindeki anons dizisini temizleyerek ve çağrının sonuç değerini döndürerek sona erer. Duyuru dizisi, düğümün çöp toplanmasını engelleyecek ve bu nedenle bağlı listedeki tüm düğümleri yığın üzerinde o noktadan canlı tutacak hiçbir düğüm referansı olmadığından emin olmak için temizlenir.
Değerlendirme yöntemi
Şimdi çağrının düğümü başarıyla dizildiğine göre çağrının değerlendirilmesi gerekir. Bunu yapmak için ilk adım, bundan önceki çağrıların değerlendirilmesini sağlamaktır. Eğer bu konu yoksa beklemeyecek, ancak hemen çalışacaktır.
EnsurePrior yöntemi
ensurePrior()
Yöntem bağlantılı listedeki önceki düğümü kontrol ederek bu işi yapar. Durumu ayarlanmamışsa, önceki düğüm değerlendirilir. Bunun özyinelemeli düğüm. Önceki düğümden önceki düğüm değerlendirilmediyse, bu düğüm için değerlendirme vb.
Artık önceki düğümün bir durumu olduğu biliniyor, bu düğümü değerlendirebiliriz. Son düğüm alınır ve yerel bir değişkene atanır. Bu başvuru null ise, başka bir iş parçacığının bunu önlediği ve bu düğümü zaten değerlendirdiği anlamına gelir; ayarlıyoruz. Aksi takdirde, önceki düğümün durumu, Sequential
bu düğümün çağrılmasıyla birlikte nesnenin uygulama yöntemine geçirilir . Döndürülen durum düğümde ayarlanır ve truncate()
yöntem artık çağrıldığında düğümden geriye doğru bağlantıyı temizleyerek çağrılır.
MoveForward yöntemi
İleri taşıma yöntemi, daha önce başka bir şeye işaret etmiyorlarsa tüm düğüm referanslarını bu düğüme taşımaya çalışır. Bu, bir iş parçacığının aramayı durdurması durumunda, başının artık gerekli olmayan bir düğüme referans tutmamasını sağlamak içindir. compareAndSet()
O ele geçirildi yana bazı diğer parçacığı bunu değişmedi eğer yöntem emin biz sadece düğüm güncelleştirme yapacaktır.
Diziyi duyurun ve yardım edin
Bu yaklaşımı basitçe kilitlemenin aksine beklemeden yapmanın anahtarı, iş parçacığı zamanlayıcısının her iş parçasına ihtiyaç duyduğunda öncelik vereceğini varsayamayacağımızdır. Her iş parçacığı kendi düğümlerini sıralamaya çalışırsa, bir iş parçacığının yük altında sürekli olarak boşaltılması mümkündür. Bu olasılığı hesaba katmak için, her bir iş parçacığı önce sıralanamayan diğer iş parçacıklarına "yardım etmeye" çalışacaktır.
Temel fikir, her iş parçacığı başarıyla düğümler oluşturdukça, atanan dizilerin monoton olarak artmasıdır. Bir iş parçacığı veya iş parçacığı sürekli olarak başka bir iş parçacığının önüne geçiyorsa, announce
dizideki sıralanmamış düğümleri bulmak için kullanılan dizin ileri doğru hareket eder. Belirli bir düğümü sıralamaya çalışan her iş parçacığı başka bir iş parçacığı tarafından sürekli olarak kullanılsa bile, sonunda tüm iş parçacıkları bu düğümü sıralamaya çalışacaktır. Örneklemek gerekirse, üç iş parçacığı içeren bir örnek oluşturacağız.
Başlangıç noktasında, üç iş parçacığının baş ve anons öğelerinin hepsi tail
düğüme yönlendirilir. lastSequence
Her bir iplik için 0'dır.
Bu noktada, Konu 1 bir çağırma ile yürütülür. Duyuru dizisini, şu anda dizine eklenmesi planlanan düğüm olan son sırası (sıfır) olup olmadığını denetler. Düğümü sıralar ve lastSequence
1 olarak ayarlanır.
İş parçacığı 2 şimdi bir çağrı ile yürütülür, anons dizisini son sırasında (sıfır) kontrol eder ve yardıma ihtiyaç duymadığını görür ve bu nedenle çağrısını sıralamaya çalışır. Başarılı olur ve şimdi lastSequence
2'ye ayarlanmıştır.
İş parçacığı 3 şimdi yürütülür ve aynı zamanda adresindeki düğümün announce[0]
zaten dizildiğini ve kendi çağrışımını dizildiğini görür . Bu oluyor lastSequence
şimdi 3'e ayarlanmıştır.
Şimdi Thread 1 tekrar çağrıldı. Dizin 1'deki anons dizisini kontrol eder ve dizinin zaten sıralandığını bulur. Aynı zamanda, Konu 2 çağrılır. Dizin 2'deki anons dizisini kontrol eder ve dizinin zaten sıralandığını bulur. Hem İş Parçacığı 1 hem de İş parçacığı 2 artık kendi düğümlerini sıralamaya çalışır. Thread 2 kazanır ve onu çağrıştırır. Bu lastSequence
4 olarak ayarlanmıştır. Bu arada, üç iplik çağrılmıştır. Dizini kontrol eder lastSequence
(mod 3) ve adresindeki düğümün announce[0]
dizilenmediğini bulur . İş parçacığı 2 ikinci iş parçacığıyla aynı anda çağrılır . Konu 1İş parçacığı 2announce[1]
tarafından yeni oluşturulan düğüm olan sıralanmamış bir çağrı bulur . Konu 2'nin çağrılmasını sıralamaya çalışır ve başarılı olur. İş parçacığı 2 kendi düğümünü bulur ve sıralanır. Bu set en öyle 5. Konu 3 sonra yerleştirilen iplik 1 düğüm o çağrılır ve buluntular olduğunu hala sıralandı değildir ve girişimleri bunu. Bu arada Thread 2 de çağrıldı ve Thread 3'ü önceden boşaltır. Düğümünü sıralar ve 6'ya ayarlar .announce[1]
lastSequence
announce[0]
lastSequence
Kötü İplik 1 . İş parçacığı 3 diziyi sıralamaya çalışsa da , her iki iş parçacığı da zamanlayıcı tarafından sürekli olarak engellendi. Ama bu noktada. Konu 2 şimdi de announce[0]
(6 mod 3) 'ü işaret ediyor . Her üç evre de aynı çağrıyı sıralamaya çalışacak şekilde ayarlanmıştır. Hangi iş parçacığı başarılı olursa olsun, sıralanacak sonraki düğüm İş Parçacığı 1'in beklemedeki çağrılması, yani başvurulan düğüm olacaktır announce[0]
.
Bu kaçınılmaz. İş parçacıklarının önceden boşaltılması için, diğer iş parçacıklarının sıralama düğümleri olması gerekir ve bu şekilde sürekli lastSequence
ilerleyeceklerdir. Belirli bir iş parçacığının düğümü sürekli olarak sıralanmazsa, sonunda tüm iş parçacıkları anons dizisindeki dizinine işaret eder. Yardım etmeye çalıştığı düğüm sıralanana kadar hiçbir iş parçacığı başka bir şey yapmaz, en kötü durum senaryosu tüm iş parçacıklarının aynı sıralanmamış düğüme işaret etmesidir. Bu nedenle, herhangi bir çağrıyı sıralamak için gereken süre girdinin boyutunun değil, iş parçacığı sayısının bir fonksiyonudur.