Java 101: Java iş parçacıklarını anlama, Bölüm 1: İş parçacıkları ve çalıştırılabilirlere giriş

Bu makale, dört bölümden oluşan Java 101 serisinin Java iş parçacıklarını araştıran ilk makalesidir . Java'da iş parçacığı oluşturmanın kavramanın zor olacağını düşünseniz de, size iş parçacıklarının anlaşılmasının kolay olduğunu göstermek niyetindeyim. Bu yazıda, size Java konuları ve çalıştırılabilirleri tanıtacağım. Sonraki makalelerde senkronizasyon (kilitler aracılığıyla), senkronizasyon sorunları (kilitlenme gibi), bekleme / bildirim mekanizması, zamanlama (öncelikli ve önceliksiz), iş parçacığı kesintisi, zamanlayıcılar, uçuculuk, iş parçacığı grupları ve iş parçacığı yerel değişkenlerini inceleyeceğiz. .

Bu makalenin (JavaWorld arşivlerinin bir parçası) Mayıs 2013'te yeni kod listeleri ve indirilebilir kaynak koduyla güncellendiğini unutmayın.

Java iş parçacıklarını anlama - dizinin tamamını okuyun

  • Bölüm 1: İş parçacığı ve çalıştırılabilir özelliklerin tanıtımı
  • Bölüm 2: Senkronizasyon
  • Bölüm 3: İş parçacığı planlama ve bekle / bildir
  • Bölüm 4: İplik grupları ve oynaklık

Konu nedir?

Kavramsal olarak, bir iş parçacığı kavramını kavramak zor değildir: bu, program kodu aracılığıyla bağımsız bir yürütme yoludur. Birden çok iş parçacığı yürütüldüğünde, bir iş parçacığının aynı koddaki yolu genellikle diğerlerinden farklıdır. Örneğin, bir iş parçacığının bir if-else deyiminin ifparçasının bayt kodunu çalıştırdığını, diğerinin ise parçanın bayt kodu eşdeğerini çalıştırdığını varsayalım else. JVM, her bir iş parçacığının yürütülmesini nasıl takip eder? JVM, her evreye kendi yöntem çağrı yığınını verir. Geçerli bayt kodu talimatını izlemeye ek olarak, yöntem çağrısı yığını yerel değişkenleri, JVM'nin bir yönteme ilettiği parametreleri ve yöntemin dönüş değerini izler.

Birden çok iş parçacığı, aynı program içinde bayt kodu komut dizilerini yürüttüğünde, bu eylem, çok iş parçacıklı olarak bilinir . Multithreading, bir programa çeşitli şekillerde fayda sağlar:

  • Çok iş parçacıklı GUI (grafik kullanıcı arabirimi) tabanlı programlar, bir belgeyi yeniden etiketleme veya yazdırma gibi diğer görevleri gerçekleştirirken kullanıcılara yanıt verir.
  • Kademeli programlar tipik olarak iş parçacığı olmayan muadillerine göre daha hızlı biter. Bu, özellikle her iş parçacığının kendi işlemcisine sahip olduğu çok işlemcili bir makinede çalışan iş parçacıkları için geçerlidir.

Java, java.lang.Threadsınıfı aracılığıyla çoklu okuma gerçekleştirir . Her Threadnesne tek bir yürütme iş parçacığını tanımlar. Bu yürütme meydana Threadbireyin run()yöntem. Varsayılan run()yöntem hiçbir şey yapmadığından, yararlı bir iş elde etmek için alt sınıflara girmeli Threadve geçersiz run()kılmalısınız. İş parçacığı ve çoklu iş parçacığı bağlamında bir tadı için ThreadListe 1'i inceleyin:

Liste 1. ThreadDemo.java

// ThreadDemo.java class ThreadDemo { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); for (int i = 0; i < 50; i++) System.out.println ("i = " + i + ", i * i = " + i * i); } } class MyThread extends Thread { public void run () { for (int count = 1, row = 1; row < 20; row++, count++) { for (int i = 0; i < count; i++) System.out.print ('*'); System.out.print ('\n'); } } }

Liste 1, sınıflardan oluşan bir uygulamaya kaynak kodunu sunar ThreadDemove MyThread. Sınıf ThreadDemo, bir MyThreadnesne oluşturarak , bu nesneyle ilişkilendirilen bir iş parçacığı başlatarak ve bir kareler tablosu yazdırmak için bazı kodlar çalıştırarak uygulamayı çalıştırır. Bunun aksine, MyThreadgeçersiz kılma Thread'nin run()metodu (standart çıkış akışı) yıldız işareti oluşan bir dik açılı üçgen baskı.

İş parçacığı planlama ve JVM

JVM uygulamalarının çoğu (tümü değilse), temel alınan platformun iş parçacığı oluşturma yeteneklerini kullanır. Bu yetenekler platforma özgü olduğundan, çok iş parçacıklı programlarınızın çıktısının sıralaması, başka birinin çıktı sırasından farklı olabilir. Bu fark, bu dizinin ilerleyen bölümlerinde inceleyeceğim bir konu olan planlamadan kaynaklanıyor.

java ThreadDemoUygulamayı çalıştırmak için yazdığınızda , JVM, main()yöntemi yürüten bir yürütme başlangıcı oluşturur . Çalıştırılması ile mt.start ();, başlangıç iplik içeren bit kodu komutları çalıştırır yürütme ikinci bir iplik oluşturmak için JVM anlatır MyThreadnesnenin run()yöntem. Tüm start()yöntem geri döndüğünde, başlangıç ipliği yürütür fordöngü yeni iplik yürütür ise, karelerden oluşan bir masa baskı run()dik açılı üçgen baskı yöntemi.

Çıktı neye benziyor? Öğrenmek ThreadDemoiçin koş . Her bir iş parçacığının çıktısının diğerinin çıktısıyla kesişme eğiliminde olduğunu fark edeceksiniz. Bu, her iki iş parçacığının da çıktılarını aynı standart çıktı akışına göndermesinden kaynaklanır.

Thread sınıfı

Çok iş parçacıklı kod yazmada uzmanlaşmak için önce Threadsınıfı oluşturan çeşitli yöntemleri anlamalısınız . Bu bölüm, bu yöntemlerin çoğunu araştırmaktadır. Spesifik olarak, evreleri başlatmak, evreleri adlandırmak, uykuya geçirmek, bir iş parçacığının canlı olup olmadığını belirlemek, bir iş parçacığını başka bir iş parçacığına birleştirmek ve geçerli iş parçacığının iş parçacığı grubundaki ve alt gruplarındaki tüm etkin iş parçacıklarını numaralandırmak için yöntemler hakkında bilgi edinirsiniz. Ayrıca Threadhata ayıklama yardımcılarını ve kullanıcı iş parçacıklarını daemon iş parçacıklarına karşı tartışıyorum .

ThreadSun'ın kullanımdan kaldırdığı yöntemler haricinde, sonraki makalelerde yöntemlerinin geri kalanını sunacağım .

Kullanımdan kaldırılan yöntemler

Sun, programlarınızı kilitleyebileceği veya nesnelere zarar verebileceği için ve Threadgibi çeşitli yöntemleri kullanımdan kaldırmıştır. Sonuç olarak, onları kodunuzda aramamalısınız. Bu yöntemlere geçici çözümler için SDK belgelerine başvurun. Bu seride kullanımdan kaldırılan yöntemleri ele almıyorum.suspend()resume()

Konu oluşturmak

Threadsekiz kurucuya sahiptir. En basitleri:

  • Thread(), Threadvarsayılan bir ada sahip bir nesne oluşturan
  • Thread(String name), bağımsız değişkenin belirttiği Threadbir ada sahip bir nesne oluşturanname

Sonraki en basit kurucular Thread(Runnable target)ve Thread(Runnable target, String name). RunnableParametrelerin yanı sıra , bu kurucular, yukarıda bahsedilen kurucularla aynıdır. Fark: RunnableParametreler Thread, run()yöntemleri sağlayan dışındaki nesneleri tanımlar . (Bunu öğrenmek Runnable, bu makalenin sonraki bölümlerinde.) Nihai dört kurucular benzerler Thread(String name), Thread(Runnable target)ve Thread(Runnable target, String name); bununla birlikte, nihai kurucular ayrıca ThreadGrouporganizasyonel amaçlar için bir argüman içerir .

Son dört kurucudan Thread(ThreadGroup group, Runnable target, String name, long stackSize)biri, iş parçacığının yöntem çağrısı yığınının istenen boyutunu belirlemenize izin vermesi açısından ilginçtir. Bu boyutu belirleyebilmek, belirli sorunları zarif bir şekilde çözmek için, özyinelemeyi (bir yöntemin tekrar tekrar çağırdığı bir yürütme tekniği) kullanan yöntemlere sahip programlarda yararlıdır. Yığın boyutunu açıkça ayarlayarak, bazen StackOverflowErrore- postaları önleyebilirsiniz . Bununla birlikte, çok büyük bir boyut OutOfMemoryErrors ile sonuçlanabilir . Ayrıca Sun, yöntem çağırma yığınının boyutunu platforma bağlı olarak görür. Platforma bağlı olarak, yöntem çağrısı yığınının boyutu değişebilir. Bu nedenle, çağıran kodu yazmadan önce programınızın sonuçlarını dikkatlice düşünün Thread(ThreadGroup group, Runnable target, String name, long stackSize).

Araçlarınızı çalıştırın

Konular araçlara benzer: programları baştan sona taşır. Threadve Threadalt sınıf nesneleri evreler değildir. Bunun yerine, adı gibi bir iş parçacığının özniteliklerini açıklar ve iş parçacığının yürüttüğü kodu (bir run()yöntem aracılığıyla ) içerirler . Yürütülecek yeni bir evre zamanı geldiğinde run(), başka bir evre Thread's veya alt sınıf nesnesinin start()yöntemini çağırır . Örneğin, ikinci bir iş parçacığı başlatmak için, uygulamanın başlattığı iş parçacığı - main()çağrıları yürütür start(). Yanıt olarak, JVM'nin iş parçacığı işleme kodu, iş parçacığının bir Thread's veya alt sınıf nesnesinin run()yöntemini düzgün şekilde başlatmasını ve çağırmasını sağlamak için platformla çalışır .

Bir kez start()tamamlanıncaya, çoklu iş parçacığı yürütmek. Doğrusal bir şekilde düşünme eğiliminde olduğumuz için , iki veya daha fazla iş parçacığı çalışırken ortaya çıkan eşzamanlı (eşzamanlı) etkinliği anlamakta genellikle zorlanıyoruz . Bu nedenle, bir iş parçacığının nerede yürütüldüğünü (konumunu) zamana göre gösteren bir grafiği incelemelisiniz. Aşağıdaki şekil böyle bir tablo sunmaktadır.

Grafik birkaç önemli zaman dilimini gösterir:

  • Başlangıç ​​iş parçacığının başlatılması
  • İş parçacığının çalışmaya başladığı an main()
  • İş parçacığının çalışmaya başladığı an start()
  • An start(), yeni bir iş parçacığı oluşturur vemain()
  • Yeni iş parçacığının başlatılması
  • Yeni iş parçacığı çalışmaya başladığı an run()
  • Her iş parçacığının sona erdiği farklı anlar

Yeni iş parçacığının başlatılmasının, çalıştırılmasının run()ve sonlandırılmasının, başlangıç ​​iş parçacığının yürütülmesi ile aynı anda gerçekleştiğine dikkat edin. Ayrıca, bir iş parçacığı çağrılarından start()sonra, yöntem çıkmadan önce bu yönteme yapılan sonraki çağrıların bir nesnenin atılmasına run()neden start()olduğunu unutmayın java.lang.IllegalThreadStateException.

Bir isimde ne var?

Bir hata ayıklama oturumu sırasında, bir iş parçacığını diğerinden kullanıcı dostu bir şekilde ayırt etmek yararlı olur. İş parçacıkları arasında ayrım yapmak için, Java bir adı bir diziyle ilişkilendirir. Bu ad varsayılan olarak Threadbir tire karakteri ve sıfır tabanlı bir tam sayıdır. Java'nın varsayılan iş parçacığı adlarını kabul edebilir veya kendinizinkini seçebilirsiniz. Özel adları Threadbarındırmak için namebağımsız değişkenler ve bir setName(String name)yöntem alan oluşturucular sağlar. Threadayrıca getName()geçerli adı döndüren bir yöntem sağlar . Liste 2, Thread(String name)yapıcı aracılığıyla özel bir adın nasıl oluşturulacağını ve run()yöntemdeki geçerli adın şu çağrılarla nasıl alınacağını gösterir getName():

Liste 2. NameThatThread.java

// NameThatThread.java class NameThatThread { public static void main (String [] args) { MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); } } class MyThread extends Thread { MyThread () { // The compiler creates the byte code equivalent of super (); } MyThread (String name) { super (name); // Pass name to Thread superclass } public void run () { System.out.println ("My name is: " + getName ()); } }

MyThreadKomut satırına isteğe bağlı bir ad bağımsız değişkeni iletebilirsiniz . Örneğin, java NameThatThread Xkurar Xparçacığının adı olarak. Bir ad belirtemezseniz, aşağıdaki çıktıyı görürsünüz:

My name is: Thread-1

Tercih ederseniz super (name);, MyThread (String name)yapıcıdaki aramayı, olduğu setName (String name)gibi bir aramaya dönüştürebilirsiniz setName (name);. Bu ikinci yöntem çağrısı, iş parçacığının adını belirleyerek aynı amaca ulaşır super (name);. Bunu senin için bir egzersiz olarak bırakıyorum.

Ana adlandırma

Java, adı main, main()yöntemi çalıştıran iş parçacığına, başlangıç ​​iş parçacığına atar . Genellikle Exception in thread "main", JVM'nin varsayılan istisna işleyicisinin, başlangıç ​​iş parçacığı bir istisna nesnesi attığında yazdırdığı mesajda bu adı görürsünüz .

Uyumak ya da uyumamak

Bu sütunun ilerleyen kısımlarında, bir hareket yanılsaması elde etmek için birbirinden biraz farklı olan bir yüzey üzerinde tekrar tekrar çizim yapan animasyonu tanıtacağım . Animasyonu gerçekleştirmek için, ardışık iki görüntünün görüntülenmesi sırasında bir iş parçacığı duraklamalıdır. ThreadStatik sleep(long millis)yönteminin çağrılması, bir iş parçacığını millismilisaniyeler için duraklamaya zorlar . Başka bir iplik muhtemelen uyku ipliğini kesintiye uğratabilir. Bu olursa, uyku iş parçacığı uyanır ve yöntemden bir InterruptedExceptionnesne fırlatır sleep(long millis). Sonuç olarak, çağrıların sleep(long millis)bir tryblok içinde görünmesi gerekir veya kodun yöntemi InterruptedException, throwscümlesinde yer almalıdır .

Göstermek için sleep(long millis)bir CalcPI1uygulama yazdım . Bu uygulama, matematiksel sabit pi'nin değerini hesaplamak için matematiksel bir algoritma kullanan yeni bir iş parçacığı başlatır. Yeni iş parçacığı hesaplanırken, başlangıç ​​iş parçacığı çağrı yaparak 10 milisaniye duraklatılır sleep(long millis). Başlangıç ​​iş parçacığı uyandıktan sonra, yeni iş parçacığının değişkene kaydettiği pi değerini yazdırır pi. Liste 3, CalcPI1kaynak kodunu sunar:

Liste 3. CalcPI1.java

// CalcPI1.java class CalcPI1 { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); try { Thread.sleep (10); // Sleep for 10 milliseconds } catch (InterruptedException e) { } System.out.println ("pi = " + mt.pi); } } class MyThread extends Thread { boolean negative = true; double pi; // Initializes to 0.0, by default public void run () { for (int i = 3; i < 100000; i += 2) { if (negative) pi -= (1.0 / i); else pi += (1.0 / i); negative = !negative; } pi += 1.0; pi *= 4.0; System.out.println ("Finished calculating PI"); } }

Bu programı çalıştırırsanız, aşağıdakine benzer (ancak muhtemelen aynı olmayan) çıktı göreceksiniz:

pi = -0.2146197014017295 Finished calculating PI