Programcı: Çok iş parçacıklı paylaşılan kaynak senkronizasyonu, dikkatli bakmazsanız pişman olacaksınız

Çoklu kullanım

Pek çok kişi multithreading kullanıyor, ancak çoğu multithreading'i henüz anlamıyor.

  • Tek işlem tek iplik: Bir kişi bir masada yemek yer.
  • Tek işlem ve çoklu okuma: birden çok kişi aynı masada birlikte yemek yer.
  • Çok işlemli ve tek iş parçacıklı: her biri kendi masasında yemek yiyen birden fazla kişi.
  • Basit bir örnek vermek gerekirse:

    Çoklu iş parçacığı açmanın sorunu, birden fazla kişi aynı anda bir yemeği yediğinde karıştırmaya yatkın olmasıdır. Örneğin, iki kişi aynı anda bir tabak tutuyor ve bir kişi yemek çubuklarını uzattı, ancak yemek ulaştığında zaten yakalandı. . . Şu anda, bir kişinin bir ısırık almasını beklemeniz ve ardından yemeği başka bir kişiye iade etmeniz gerekir, bu da kaynak paylaşımı için çatışmalar ve rekabet olacağı anlamına gelir.

    O halde kaynakları paylaşmaktan bahsedeyim!

    Paylaşılan kaynak senkronizasyonu

    Çok iş parçacıklı geliştirmedeki en sıkıntılı sorun, muhtemelen paylaşılan kaynakların kontrolüdür Bu konuyu bugün konuşalım.

    Adından da anlaşılacağı gibi, paylaşılan kaynaklar, birden çok iş parçacığı tarafından kullanılması gereken kaynaklardır, ancak çoğu durumda, birden çok iş parçacığının bu kaynağı aynı anda kullanmasına izin veremeyiz. Bu genellikle beklenmeyen sorunlar yaratır. Aşağıdaki örneği ele alalım:

    paket com.mfs.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; / * * Bu soyut bir jeneratör sınıfıdır * / abstract class Generator { özel boole iptal edildi = yanlış; public abstract int next (); // next () yalnızca arayüzü verir, uygulama yoktur, bu da üretilen sınıfta uygulanmalıdır genel geçersiz iptal () { // Üreticiyi iptal edin, bu yöntemin yalnızca bir atama deyimine sahip olduğunu görebilirsiniz, bu atomik bir işlemdir, yani bu yöntem çağrılırken iş parçacığı asla kesilmeyecektir iptal edildi = doğru; } public boolean isCanceled () { // Jeneratörün iptal edilip edilmediğini döndür, bu aynı zamanda atomik bir işlemdir iade iptal edildi; } } / * * Asıl amaç bir çift sayı üreteci yazmaktı, ancak bu oluşturucunun yalnızca çift sayı üretemeyeceği ortaya çıktı. * Tam sayı üreteci, ancak asıl amacımızın eşit bir üreteç yazmak olduğunu hatırlamanız gerekir. * / class IntGenerator Generator'ü genişletir { private volatile int currentData = 0; // Geçerli değer. Uçucu anahtar kelime daha sonra tanıtıldığından, şu anda onu önemsemenize gerek yoktur. Var olması için bir nedeni olmasına rağmen @Override / * * Sonraki yöntem sonraki çift sayıyı döndürecektir * Ancak bunda birçok ifade olduğunu ve artırma operatörünün kullanıldığını görebilirsiniz, bu nedenle bu yöntem atomik bir işlem olmamalıdır * Yani, bir iş parçacığı bu yöntemi çalıştırdığında, bir yerde sonlandırılma olasılığı çok yüksektir. * İlk artımın yürütülmesinin sonlandırıldığını varsayalım (currentData'nın değeri şu anda tek sayı olmalıdır, 3 olduğu varsayılarak) * Bir sonraki işlem bu yönteme girip iki kez arttığında, döndürülen değer tek sayı 5'tir ve bu, çift oluşturucunun orijinal niyetiyle uyumlu değildir. * Aynı kaynağı aynı anda paylaşan birden çok iş parçacığının inanılmaz hatalar üreteceğini de kanıtlıyor. * / public int next () { // TODO Otomatik oluşturulan yöntem saplaması currentData ++; Thread.yield (); // Burada yied yöntemi eklemek, iş parçacığının kesintiye uğradığı ve atomik bir işlemin hangi işlemin belirli işletim sistemi ve JVM ile ilgisi olduğu burada iş parçacığı kesintisi olasılığını artırmaktır. // Yani, eğer bu eklenmezse, bazı durumlarda çakışma olmadan mükemmel bir dizi oluşturulabilir (benim bilgisayarım böyle), ancak paylaşılan kaynakları kullanarak iş parçacığı çatışmasını çözmek için buna güvenmemeliyiz currentData ++; currentData döndürmek; } } class ConsumeInt Runnable { private static int taskCount = 0; private final int id = taskCount ++; // iş parçacığı kimliği private Generator generator; // Oluşturucu nesnesi tutuldu public ConsumeInt (Generator gen) { // TODO Otomatik oluşturulan yapıcı saplaması jeneratör = gen; } @Override public void run () { // TODO Otomatik oluşturulan yöntem saplaması while (! generator.isCanceled ()) {// Oluşturucu iptal edilmediği sürece, daima bir sonraki değeri çıkar int n = generator.next (); if (n% 2! = 0) {// Alınan değer tek bir sayı ise, bu, jeneratörün bir iş parçacığı çakışması olduğu anlamına gelir, o zaman oluşturucuyu iptal edin generator.cancel (); System.out.println ("#" + id + "Oluşturucu iptal edildi" + n); } System.out.print ("#" + id + "(" + n + ")"); // Alınan değeri şu biçimde çıktı: #id (değer) } } } public class Test { public static void main (String args) { ExecutorService havuzu = Executors.newCachedThreadPool (); Jeneratör oluşturucu = yeni IntGenerator (); for (int i = 0; i < 5; i ++) {// Hepsi aynı oluşturucu nesneyi tutan beş iş parçacığı açın pool.execute (yeni ConsumeInt (jeneratör)); } pool.shutdown (); } }

    sonuç:

    Çatışmanın gerçekten meydana geldiği görülebilir.İş parçacığı ilk önce sonraki yönteme girdi, ancak yalnızca bir artış işlemi gerçekleştirdikten sonra kesildi.Şu anda, currentData değeri 1'dir. Ardından 0 iş parçacığı bir sonraki yönteme tekrar girer ve bu yöntemi bir seferde yürütür ve dönüş değeri iki kez artırıldıktan sonra 3'tür ve 0 iş parçacığı oluşturucuyu iptal eder. Ancak iplik 1 sonraki yönteme girdi, bu nedenle jeneratör iptal edilse bile, yine de ikinci artışı gerçekleştirebilir ve sonunda 4'ü döndürür.

    Bunun olmasını önlemek için ne yapabiliriz? Aslında hala yöntemler var. İki genel fikir var. Birincisi, birden çok iş parçacığının kaynakların denetimini doğrusal olarak elde etmesini sağlamaktır.Diğer iş parçacığı, önceki iş parçacığı denetimi bırakmadan önce beklemelidir; ikincisi, paylaşılan kaynakları oluşturmaktır Birden fazla yedekleme için, her iş parçacığı bir yedekleme ile ilişkilendirilmelidir, ancak bu, hiç şüphesiz iş parçacıkları arasındaki iletişimi engelleyecektir.

    Yukarıdaki fikirlere göre çatışma sonlandırmanın birçok yolu vardır. Ardından, ilk fikirleri tek tek tanıtacağız:

    Kaynak senkronizasyonu (senkronize edilmiş)

    Senkronize yöntem aynı anda yalnızca bir iş parçacığı tarafından kullanılabilir.Eğer bir nesnenin eşitlenmiş bir yöntemi 0 iş parçacığı tarafından kullanılıyorsa, aynı nesnedeki tüm eşitlenmiş yöntemler diğer evreler tarafından kullanılamaz.

    Bir evre bir senkronizasyon kaynağının kontrolünü elde ettikten sonra, kaynak için kilit sayısı 1 artacaktır ve eğer evre nesnenin diğer senkronizasyon kaynaklarını kullanıyorsa, kilit sayısı 1 artacaktır. Kullanımdan sonra, 0'a düşene kadar kilit sayısı bir azalacaktır. Yalnızca zincir sayısı sıfır olan kaynak diğer konular tarafından kontrol edilebilir.

    Yukarıdaki örnek için, iş parçacığı çakışmasını çözmek için sadece next () yöntemini eşitlenmiş anahtar sözcükle değiştirmemiz gerekiyor. Değiştirilmiş IntGenerator sınıfının sonraki yöntemi aşağıdaki gibidir:

    public synchronized int next () {// Bu yöntem, yalnızca geçerli iş parçacığı bu yöntemi kullanarak bittikten sonra diğer evreler tarafından kullanılabilir // TODO Otomatik oluşturulan yöntem saplaması currentData ++; Thread.yield (); currentData ++; currentData döndürmek; }

    Açık Kilit nesnesi

    Lock nesnesinin paylaşılan kaynakları da kilitlediğini gösterin.Senkronize ile karşılaştırıldığında, Lock nesnesi yöntemin bazı kod parçalarını kilitlememize izin verir (senkronize etmek de mümkündür, daha sonra tanıtacağız); Kilit nesnesi aynı zamanda kilidi almayı denememize izin verir, eğer değilse Edinme, bekleme süresini azaltmak için başka görevleri de gerçekleştirebilir ve Kilit nesnesi, kontrol nesnesi anormal olduktan sonra bazı işlemleri gerçekleştirmemizi de sağlayabilir.

    Lock nesnesini kullanarak ilk örnekteki kodu değiştirelim. Değiştirilen IntGenerator sınıfı aşağıdaki gibidir:

    class IntGenerator Generator'ü genişletir { özel uçucu int currentData = 0; özel Kilit kilidi = yeni ReentrantLock (); @Override public int next () { // TODO Otomatik oluşturulan yöntem saplaması lock.lock (); Deneyin { currentData ++; Thread.yield (); currentData ++; currentData döndürmek; } en sonunda { // Bir istisna olursa, yürütülmesi garanti edilebilir lock.unlock (); // Dönüşten önce asla kilidi açmayın, bu, iş parçacığının dönmeden önce kesilmesine neden olabilir } } }

    Atomik işlem

    Her yöntem için yalnızca bir ifade varsa ve bu bir atomik işlemse, o zaman bir iş parçacığı yönteme girdikten sonra, diğer iş parçacıklarına girme şansı vermeden yürütmenin hemen tamamlandığı da garanti edilebilir.

    Ancak çok az atomik işlem vardır.Bilebileceğimiz tek atomik işlemler, geri dönüş ve atama işlemleridir (işletim sistemi ve JVM, atomik işlemlerde farklılık gösterir) ve daha az atomik veri türü vardır.

    Atomik olmayan veri türlerini atomik veri türlerine dönüştürmek için volatile anahtar sözcüğünü kullanın. Uçucu, atomikliği sağlamanın yanı sıra, uygulama görünürlüğünü sağlamada, yani veride yaptığımız her değişikliğin sadece tampondaki değeri değiştirmek yerine ana bellekteki verilerde bir değişiklik olmasını sağlamak için daha önemli bir role sahiptir. . Makalenin başındaki örnekte, bizim uçucu kullanımımız, kullanımın görünürlüğüdür, yani, currentData her artırıldığında, ana bellekteki verilerde bir değişiklik olmasını sağlamak, böylece sonraki eklenen iş parçacığı önceki iş parçacığı değişikliğini okuyabilir. Sonraki değer, orijinal değer değildir.

    Atomiklik çok ezoterik bir problemdir, bu yüzden bir uzman değilseniz, kaynak senkronizasyon problemlerini çözmek için atomikliği kullanmanızı kesinlikle önermiyoruz.

    Sorunu çözmek için atomikliği kullanmanız gerekiyorsa, Java tarafından sağlanan AtomicInteger, AtomicLong, AtomicReference vb. Gibi atomik sınıfları kullanmanızı öneririz. Aşağıda, IntGenerator'ü değiştirmek için atomik sınıfları kullanıyoruz:

    class IntGenerator Generator'ü genişletir { private AtomicInteger currentData = new AtomicInteger (0); @Override public int next () {// İş parçacığı, önce currentData'ya 2 eklemek için bu yönteme girer ve ardından değeri almak için next'i çağırır. Ortada kesintiye uğrayabilir, ancak hiçbir zaman tek sayı üretmez // TODO Otomatik oluşturulan yöntem saplaması currentData.get () döndür; } public void increament () { currentData.addAndGet (2); // AtomicIntger tarafından sağlanan yöntemler temelde atomiktir } }

    Kritik bölüm (senkronize edilmiş kullanarak)

    Yukarıda bahsedildiği gibi, senkronize edilmiş bazı kodların senkronizasyonunu da sağlayabilir. Şimdi uygulayalım:

    class IntGenerator Generator'ü genişletir { private int currentData = 0; @Override public int next () { currentData ++; currentData ++; currentData döndürmek; } } }

    İş arkadaşları eşzamanlılığın zor olduğunu söylüyor, Java eşzamanlılığı da öyle mi? Eşzamanlılık mekanizmasının altında yatan üç ilke
    önceki
    Programcı: İş parçacığını nasıl doğru bir şekilde durduracağınızı hala bilmiyor musunuz?
    Sonraki
    İyi makale paylaşımı: JavaWeb'de size yönetici oturum açma bilgilerini ve CURD bilgilerini göstermek için 5 resim
    Programcı: Nesnenin özünü ortaya çıkarmak için Java'nın 5 özelliği
    Çin'in bilim ve teknolojisinin bir başka güzel haberi daha var, kuantum çip teknolojik sınırı aşıyor ve dünya hemen köşede!
    Maskeyi litografi makinesi ile değiştirin! Hollanda kontrolden çıktı ve yardım istiyor, Rusya: Çin'in büyük bir fırsatı var
    Uyarılmış! Çin bağımsız olarak gelecekte artık kısıtlanmayacak bir "kuantum çipi" geliştirdi
    14nm işleminin seri üretiminin ardından, Çin'in çipi 6nm'lik buz darboğazını kırarak başka bir iyi habere sahip
    inanılmaz! Çok az yerli litografi makinesi üreticisi var ve bu görünmez dev bize umut veriyor.
    Çin Bilimler Akademisi başka bir büyük atılım yaptı! Batılı ülkeler buna inanamıyor, Çin bunu nasıl yaptı?
    Du Yuesheng, karısının "raydan çıktığını" ve idarenin basit ve zorba olduğunu ve bir Şangay kahramanı olmayı hak ettiğini buldu.
    Programcı: Altta yatan bilgisayar analiziyle birleştirilmiş "İplik Güvenliği İlkelerinin Analizi"
    Programcı: Basit tutun, Class dosyasının JVM'ye nasıl yüklendiğini tam olarak anlamanıza izin verin
    Lojistik şirketleri kapanmıyor, devlet şirketleri hastalıkla savaşmak için el ele veriyor
    To Top