You are on page 1of 10

Java Concurrency

Javada paralel olarak birden fazla ilem yapabilmek iin threadler kullanlr. Yeni bir thread yaratmak ve balatmak ok kolaydr. in asl zorluu ise bu threadlerin ayn veriyi paylamasdr.

Runnable, Thread
Javada yeni bir thread balatmak iin nce Runnable interfaceini implement eden bir snf yazmak gerekir.
Runnable runnable = new Runnable(){ public void run(){ // Do something } } Thread thread = new Thread(runnable); thread.start();

Bu kod run methodunun ierisindeki blou yeni bir thread iinde alacaktr. Thread balatmak ok kolaydr ancak iki threadin ayn veriyi paylamas iin zor ksmdr. Bu i iin javada kullanlan baz keywordler vardr. Bunlardan ilki final keywordudr. Bir snfa ait tm final yeler deitirilemez olduundan threadler bu yelere eriirken dnmek gerekmez. Bir thread bir snfn yesinin deerini deitirdiinde bu deer annda tm threadler iin okunabilir olmayabilir. Deeri deitiren thread iin ayn deer okunduunda herhangi bir sorun olmaz ancak baka bir thread iin 3 farkl durum szkonusudur. Deerin eski halini okuyabilir, yeni halini okuyabilir veya ksmen yazlm yar eski yar yeni bir deer okuyabilir. Bu durum ok tehlikeli olabilir. Dahas bir snfa ait birden fazla yenin birlikte deitirilmeleri ve ilem tamamlanmadan hibirinin okunamaz olmas gerekebilir nk ilem srasnda veriler tutarsz olacaktr. Bu gibi durumlar iin javada 2 keyword daha vardr; synchronized ve volatile. volatile keywordu bir yenin deeri deitirildiinde dier tm threadler iin de geerli olmasn salar.
class MyRunnable implements Runnable{ private volatile boolean run=true; public void run(){ while(run){ // Do something in loop } public void stop(){ run=false; } }

Yukardaki rnekte volatile keywordu kullanlmayacak olursa stop methodu arldktan sonra bile run methodu ierisindeki dng bir sre almaya devam edebilir.

synchronized keywordu ise ayn anda ancak bir thread tarafndan girilebilecek bloklar oluturur.
public class Circle{ private double radius; private double area; public void setRadius(double radius){ synchronized(this){ this.radius = radius; this.area = Math.PI * radius * radius; } } public double getArea(){ synchronized(this){ return area; } } public double getRadius(){ synchronized(this){ return radius; } } }

setRadius methodu arldnda iindeki synchronized blou sayesinde radius ve area birlikte set edilmeden getArea veya getRadius methodlar cevap vermeyecektir. Bylece okunan deerler tutarl olacaktr. Ayn zamanda volatile keywordunun yapt gibi synchronized blou bittii noktada o nesnenin tm yeleri dier threadler iin okunabilir olur. Yani synchronized keywordu kullanld iin volatile keywordunu kullanmaya gerek yoktur. synchronized keywordu method tanmnda da kullanlabilir. Bu o methodun ieriinin tamamn synchronized(this) ierisine almakla ayn eydir. Yani
public void synchronized setRadius(double radius){ this.radius = radius; this.area = Math.PI * radius * radius; }

ayn anlama gelecektir. imdi aadaki kod blounu inceleyelim;


double area = circle.getArea(); double radius = circle.getRadius(); // Do something with the area and radius

Buradaki problem tahmin edeceiniz gibi getArea ile getRadius arasnda baka bir thread setRadius methodunu arrsa ne olur? Cevap basit bu haliyle tutarsz veriler elde edebiliriz. Bu sorunun zm getArea ve getRadius methodlarn synchronized blou ierisinde armaktr.
double area; double radius; synchronized(circle){ area = circle.getArea(); radius = circle.getRadius(); } // Do something with the area and radius

Bylece getRadius ve getArea arasnda setRadius methodunun arlmas mmkn olmayacaktr.

Wait, Notify, Interrupt


Bu keywordler temelde javada bir threadin akn duraklatmak ve devam ettirmek iin kullanlr. Klasik bir producer-consumer senaryosu inceleyelim. Senaryoya gre bir thread belli bir kurala gre saylar retecek, baka bir thread ise bu saylar ileyecek.
import import import import java.security.SecureRandom; java.util.LinkedList; java.util.Queue; java.util.Random;

public class ConsumerProducer { private static final Queue QUEUE = new LinkedList(); private static final Runnable NUMBER_PRODUCER = new Runnable() { private final Random random = new SecureRandom(); @Override public void run() { for (int i = 0; i < 1000; i++) { synchronized (QUEUE) { QUEUE.add(random.nextInt()); QUEUE.notify(); } } synchronized (QUEUE) { THREAD_NUMBER_CONSUMER.interrupt(); } System.out.println("Producer done!"); } }; private static final Runnable NUMBER_CONSUMER = new Runnable() { @Override public void run() { while (true) { int i; synchronized (QUEUE) { if (QUEUE.isEmpty()) { if (Thread.currentThread().isInterrupted()) break; try { QUEUE.wait(); } catch (InterruptedException e) { System.out.println("Interrupted!"); } continue; } else { i = QUEUE.poll(); } } // Do something with the i value System.out.println(i);

} System.out.println("Consumer done!"); } }; private static final Thread THREAD_NUMBER_PRODUCER = new Thread( NUMBER_PRODUCER, "Number Producer"); private static final Thread THREAD_NUMBER_CONSUMER = new Thread( NUMBER_CONSUMER, "Number Consumer"); public static void main(String[] args) throws InterruptedException { System.out.println("Starting..."); THREAD_NUMBER_CONSUMER.start(); THREAD_NUMBER_PRODUCER.start(); THREAD_NUMBER_CONSUMER.join(); THREAD_NUMBER_PRODUCER.join(); System.out.println("Done."); } }

Wait notify mekanizmas zetle u ekilde alr; ncelikle iki thread birlikte ayn nesneyi synchronize ederler (bizdeki rnekte QUEUE synchronize edilmitir). Yukarda anlatld gibi threadlerden ancak bir tanesi bloun iine girerken dieri beklemeye balar. Ancak threadlerden biri wait ettiinde iler deiir. Wait methodunu aran thread artk kendisi beklemeye balar ve dier thread synchronized blounun iine girebilir. Bekleyen thread ancak dieri tarafndan notify veya interrupt edildiinde tekrar uyanr ve ileme devam edebilir. Bir thread bir dierini notify ettiinde kendisi beklemeye balar ta ki notify edilen thread tekrar wait edene veya synchronized blounu terk edene kadar. Bu ilemlerin ana fikri udur; producer yeni bir veri rettii anda o veriyi bekleyen dier threadleri uyandrr ki veriyi ekip ileme koysunlar. Interrupt notify gibi bekleyen threadi uyandrr ancak bekleyen thread InterruptedException alacaktr. Ayrca bir thread bekledii nesne synchronize edilmeden interrupt edilebilir. Genellikle bir ilemi sonlandrmak amacyla kullanlr.

GUI Thread, invokeLater, invokeAndWait, isEventDispatchThread


Bir swing uygulamas yazyor iseniz tm kullanc girilerini ileyen, ekranlar monitre izen zel bir thread vardr. Bu threade GUI Thread denir. Swing bileenlerine ve onlarn alanlarna GUI threadi dnda erimek tehlikelidir. Yani bir labelin textini kendi balattnz bir thread iinde set etmek doru deildir. Threadler ile almann en kt yn bireyleri yanl yapmanz durumunda problemin kendini hep deil bazen gsterecek olmasdr. yle ise swing bileenlerine GUI threadi dnda ulamamaya ok zen gstermek gerekir. Dier yandan GUI threadi ierisinde uzun zaman alan iler de yapmak doru deildir nk bu sre zarfnda uygulama ne kullanc girilerine cevap verebilecek ne de pencere ieriklerini gncelleyebilecektir. yle ise uzun sren ilemleri ayr threadler araclyla yapmak daha dorudur. Bu noktada akla gelen soru ise udur threadin yapt ilemlerden kullanyc nasl haberdar edeceiz? Bu noktada SwingUtilities snfnn invokeLater ve invokeAndWait methodlar devreye girer. Aadaki rnei ele alalm.

import java.awt.BorderLayout; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class CopyFile { public static void main(String[] args) { final JLabel label = new JLabel(); JFrame frame = new JFrame(); frame.getContentPane().add(label, BorderLayout.CENTER); frame.setSize(200, 80); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); Runnable runnable = new Runnable() { @Override public void run() { copyFiles(label); } }; new Thread(runnable).start(); } private static void copyFiles(final JLabel label) { for (int i = 0; i < 10; i++) { final String fileName = "Picture" + i + ".jpg"; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { label.setText(String.format("Coppying \"%s\"...", fileName)); } }); // Copy file here try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { label.setText("Done."); } }); } }

invokeLater methodu ierisine geilen runnable nesnesini sonra almak zere kuyrua ekleyecek ve hemen altndaki satrdan devam edecektir. Burada unutulmamas gereken invokeLater methodu hemen dner, thread bir alttaki satra getiinde run methodunun iindeki blok muhtemelen henz iletilmeye balamam olacaktr. yle ise invokeLater, swing bileenlerinden bilgi almak amacyla kullanlamaz. Bu durumlar iin invokeAndWait methodu kullanlmaldr. Aadaki rnekte getTextFieldValue methodu farkl bir thread iinden arlsa bile sorunsuz alacaktr.

import java.awt.BorderLayout; import java.lang.reflect.InvocationTargetException; import javax.swing.JFrame; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class SomeFrame extends JFrame { private static final long serialVersionUID = 1L; private final JTextField textField = new JTextField(); public SomeFrame() { getContentPane().add(textField, BorderLayout.CENTER); } public String getTextFieldValue() { if (SwingUtilities.isEventDispatchThread()) { return textField.getText(); } else { final String[] s = new String[1]; try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { s[0] = textField.getText(); } }); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } return s[0]; } } }

invokeAndWait methodu run methodunun iindeki blok tamamlanana kadar aran threadi bekletir. Bylece return edilen satra gelindiinde blok alm olacaktr. Burada unutulmamas gereken bir baka konu da udur; invokeLater GUI threadi iinden de sorunsuzca arlabilir ancak invokeAndWait GUI threadi ierisinden arlacak olursa program kitlenir. Bunun olmamas iin rnekte isEventDispatchThread ile GUI threadi ierisinde olup olmadn kontrol ediliyor. Bylece getTextFieldValue GUI threadi ierisinden arlm olsa bile program kitlenmeyecektir. String dizisi bu kodda ok ho grnmyor AtomicReference kullanmak daha doru olurdu ancak Atomic snflar henz anlatmadm iin kullanmamay tercih ettim.

ThreadLocal
ThreadLocal snf zellikle sunucu uygulamalarnda statik deikenler ierisinde saklamak istediimiz kullanc, veritaban balants gibi bilgileri kolayca saklamamz salar. Bir web sunucusu dnelim. Kullanc bilgisini servlet ierisinde oluturduk ve bu bilgiyi sistemin geneliyle paylamak istiyoruz. Static bir deikene atamak ve sistemde bu deikeni kullanmak tehlikeli olacaktr nk web sunucusu ayn anda birden fazla istemciye cevap verir. Bu da hemen ardndan balanan dier istemcinin bir ncekinin deerinin zerine yazmasna sebep olur. Ancak biliyoruz ki ayn anda balanan iki istemciye cevap veren

threadler farkl olacaktr. ThreadLocal snfnn get ve set methodlar vardr. Set methodunu aran thread dier threadlerin ilgili ThreadLocal nesnesine ne set ettiinden bamsz olarak get methodunu ardnda kendi set ettii deeri okuyacaktr. Aadaki rnei inceleyelim;
public class ThreadLocalTest { final static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); static class MyRunnable implements Runnable { final Integer value; public MyRunnable(Integer value) { this.value = value; } @Override public void run() { threadLocal.set(value); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } if (threadLocal.get().equals(value)) { System.out.println("Match"); } else { System.out.println("Not match"); } } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(new MyRunnable(i)).start(); } } }

Bu rnei altracak olursanz 10 adet Match yazdn greceksiniz.

Atomic classes
Atomic snflar zetle verisi thread safe olarak deitirilebilir nesnelerdir.
class MyRunnable implements Runnable{ private final AtomicBoolean run=new AtomicBoolean(true); public void run(){ while(run.get()){ // Do something in loop } public void stop(){ run.set(false); } }

Bu rnek volatile konusunda verilen rnekle edeerdir ancak set ve get methodlar zaten synchronized olduu iin ayrca volatile demek gerekmez. Ayrca Atomic snflar .net deki out parametre gibi kullanmak da mmkndr.

ExecutorService, Executors, Callable, Future


Bu snflar ve interfaceler yardmyla thread ve runnable snflar kullanmadan baka threadler iinde bloklar altrlabilir ve altran thread ile sonular paylalabilir. Aadaki rnei ele alalm;
package com.datasel.progressstudy; import import import import import import java.math.BigDecimal; java.util.concurrent.Callable; java.util.concurrent.ExecutionException; java.util.concurrent.ExecutorService; java.util.concurrent.Executors; java.util.concurrent.Future;

public class FactCalc { private static final class FactorialCalculator implements Callable<BigDecimal> { private final int x; public FactorialCalculator(int x) { this.x = x; } @Override public BigDecimal call() throws Exception { BigDecimal fact = new BigDecimal(1); for (int i = 2; i < x; i++) { fact = fact.multiply(new BigDecimal(i)); } return fact; } } public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newCachedThreadPool(); Future<BigDecimal> f100 = executorService .submit(new FactorialCalculator(100)); Future<BigDecimal> f200 = executorService .submit(new FactorialCalculator(200)); BigDecimal r100 = f100.get(); BigDecimal r200 = f200.get(); System.out.println(" 100! = " + r100.toPlainString()); System.out.println(" 200! = " + r200.toPlainString()); System.out.println("100! + 200! = " + r100.add(r200).toPlainString()); executorService.shutdown(); } }

Bu rnekte 100 ve 200 saylarnn faktriyelleri ayr threadlerle hesaplanr bylece bilgisayarn ift ilemcisi varsa ikisi birlikte hesaplanr ve daha hzl sonu retir. ExecutorService e ait submit methodu arldnda call methodu ierisindeki blok yeni bir thread ierisinde altrmaya balar ve bir Future nesnesi dner. Bu nesne call methodunun dn deeri (veya rettii exception) u saklar. Ayn zamanda ilemin bitip bitmediiyle ilgili bilgilere de sahiptir. Futurea ait get methodu arldnda eer call methodunun almas tamamlanmam ise bitene kadar aran threadi bloke eder ve tamamland anda call methodunun dndrd deeri dndrr (veya exception frlatr).

Lock, ReadWriteLock
Lock synchronized bloklarna alternatif olarak dnlebilir ama birka fazladan zellii vardr. Ancak burada unutulmamas gereken nokta synchonized keywordu gibi nesnenin alanlarn blok sonunda dier threadler iin grnr klmayacaktr. Bu sebeple atomic snflarla veya volatile alanlarla birlikte kullanlmalar gerekir. Daire rneine tekrar bakalm.
public class Circle { public final Lock lock = new ReentrantLock(); private volatile double radius; private volatile double area; public void setRadius(double radius) { lock.lock(); try { this.radius = radius; this.area = Math.PI * radius * radius; } finally { lock.unlock(); } } public double getArea() { lock.lock(); try { return area; } finally { lock.unlock(); } } public double getRadius() { lock.lock(); try { return radius; } finally { lock.unlock(); } } }

Lock kullanrken unlock methodunu armak yazlmcnn sorumluluundadr. Doru yaplmazsa unlock edilmez ve lock etmeye alan tm threadleri kitler. Ancak bu dezavantajnn yannda birok avantaj vardr. rnein tryLock methodu eer kitleyebiliyor ise devam etmesini salar eer zaten lock edilmise threadi bloke etmeyecektir. Ayrca ReadWriteLock ok nemli kullanm alanlarna sahiptir. Aadaki rnee bakalm.

public class Circle { public final ReadWriteLock lock = new ReentrantReadWriteLock(); private volatile double radius; private volatile double area; public void setRadius(double radius) { lock.writeLock().lock(); try { this.radius = radius; this.area = Math.PI * radius * radius; } finally { lock.writeLock().unlock(); } } public double getArea() { lock.readLock().lock(); try { return area; } finally { lock.readLock().unlock(); } } public double getRadius() { lock.readLock().lock(); try { return radius; } finally { lock.readLock().unlock(); } } }

Bu rnekte get methodlar ayn anda birden fazla method tarafndan birbirlerini bloke etmeksizin arlabilir. Ancak bir thread readLock almken bir bakas writeLock alamaz. Bu hem performans arttrr hem de tutarsz veri olmasn engeller. Ancak bu noktada dikkat edilmesi gereken bir konu vardr; eer bir kod blou nce readLock sonra writeLock alrsa bu kitlenemelere sebep olabilir. Ayrca bunlarn dnda threadler ile kullanlmak zere BlockingQueue, BlockingDequeue, CopyOnWriteArraySet , CopyOnWriteArrayList, CyclicBarrier, CountDownLatch, Semaphore, Exchanger vb.. snflar da vardr ancak bu snflarn kullanm benim konumun dnda kalyor. Daha fazlasn merak ediyorsanz veya paylamak istediiniz bireyler var ise buraya yorum ekleyebilirsiniz.