2 Mayıs 2015 Cumartesi

Exception handling hakkında genel bilgiler

Java exception’un iki turu nedir? Bu iki tur arasindaki farklar nelerdir?


Exception ve Error’lar arasindaki fark nedir?


throw ve throws arasindaki farklar nelerdir?


Exception handling sonunda bulunan finally blogunun onemi nedir?


finalize() metodu ile finally blogu arasindaki farklilik nedir?


JDBC hakkında genel bilgiler

JDBC Nedir?


JDBC driver’larinin rollerini aciklayiniz?


Class.forName metodunun amacini aciklayiniz?


PreparedStatement’in Statement uzerindeki avantaji nedir?


CallableStatement kullanimi nasildir?


Connection pooling ne demektir?


Servlet hakkında genel bilgiler

Servlet Nedir? Bir Servlet mimarisi nasil olmalidir?


Servlet ve Applet arasindaki farklar nelerdir?


GenericServlet ve HttpServlet arasindaki farklar nelerdir?


Servlet yasam dongusunden bahsedin?


doGet() ve doPost() arasindaki farklar nelerdir?


Web Application ne dir?


SSI, Server Side Include yapisini aciklayin?


Servlet Chaining nedir?


Client makinelerin servlet’lere istek gondermesinden bahsedin?


HTTP’nin yanit verme adimlarindan bahsedin?


Cookie nedir?


HTTP Tunneling nedir?


sendRedirect ve forward metodlari arasindaki farklar nelerdir?


URL Encoding ve URL Decoding yapilari nelerdir, aciklayiniz?


JSP hakkında genel bilgiler

JSP Page nedir?
JSP, yani Java Server Pages, HTML ile yazılmış ve içinde java kodu barındırabilen dinamik web sayfalarıdır (web component). *.jsp uzantılı bir html sayfası içerisinde <% %> tagları içine java kodu yazılır.
Bu sayfa baştan sona taranarak container tarafından arka planda bir servlete (yani java dosyasına) dönüştürülür. Ve o şekilde çalıştırılır.

JSP ile calismanin avantajlari nelerdir?
JSP'nin avantajı servlette olduğu gibi java kodu içinde html yazılmadığından karmaşıklığın az olmasıdır. Hem yazılması, hem de okunması servlete göre daha kolaydır.
Özellikle JSTL tagları kullanıldığında spagetti kod olmaktan çıkıp, çok daha okunaklı hale gelir.

JSP Request’lerinin islem adimlari nelerdir?
Browser bir jsp sayfasını çağırır.

1. Compilation

Container ilgili sayfanın daha önceden compile edilip edilmediğine bakar.
Eğer hiç compile edilmediyse, ya da edildikten sonra üzerinde değişiklik yapıldıysa, compile eder.
Compile işlemi 3 aşamalıdır: önce jsp sayfası parse edilir, servlete çevrilir, oluşan servlet compile edilir.

2. Initialization

Requeste cevap vermeden önce, container jsp sayfasını ilk kez yüklüyorsa jspInit() metodu ile initialization işlemi yapar (servlet'teki init() metodunda olduğu gibi).

Burada veritabanı bağlantıları kurulması, gerekli dosyaların açılması vb işlemler yapılır.

3. Execution

Bir jsp sayfası çağırıldığında, loading ve initialization işlemleri bittikten sonra container jsp'deki jspService() metodunu çağırır.
Bu metod HTTPServletRequest ve HTTPServletResponse parametreleri alır.
Bu metod her request için bir kez çalıştırılır.

4. Cleanup

Container jspDestroy() metodu ile jsp'yi yokeder. Bu metodu override ederek veritabanı bağlantılarını kapatma, açık dosyaları kapatma gibi işlemleri yapabiliriz.

Directive’ler nelerdir? JSP’te mevcut directive’ler nelerdir?


JSP action’lar nelerdir?


Scriptlet, Declaration ve Expression’lar nelerdir?


implicit objects nedir, ne ise yarar?


21 Nisan 2015 Salı

Java Collections

http://docs.oracle.com/javase/8/docs/technotes/guides/collections/reference.html
http://www.journaldev.com/1330/java-collections-interview-questions-and-answers
http://en.wikipedia.org/wiki/Java_collections_framework

https://docs.oracle.com/javase/tutorial/collections/intro/index.html
...

Collection birden fazla elemanı birarada tutmaya yarayan bir nesnedir.
Collections frameworkleri collectionların gösterilmesi ve değiştirilmesi için çeşitli interface, implementasyon ve algoritmalar barındırır.

Interfaces


Java Collections Framework'un temelinde core collection interface'leri vardır. Core collection interface'leri bir hiyerarşi içerisinde birçok farklı collection tipini enkapsüle eder. Bu interfaceler sayesinde collectionlar kendi detaylarından bağımsız olarak manipüle edilebilir.


...

Set özel bir Collection türüdür; SortedSet özel bir Set türüdür. Hiyerarşide iki ayrı ağaç bulunur: Map bir Collection değildir.

Bütün core collection interfaceleri generic'tir. Örneğin Collection interface'inin deklarasyonu şöyledir:

public interface Collection...

Buradaki syntax bu interface'in generic olduğunu belirtir.
Bir Collection instance'ı yarattığımızda içinde tutulacak nesne tipini belirtmek zorundayız.
Tip bilgisini belirtmek compiler'ın daha compile-time aşamasında collectiona konulan nesne tiplerinin doğruluğunu kontrol etme imkanı verir.
Bu sayede runtime'da oluşabilecek hataların önüne geçilmiş olur.

Java platformu her collection türünün alt türleri için ayrı ayrı interface'ler tanımlamamıştır. Bunun yerine, her bir interface'de opsiyonel operasyonlar tanımlanmıştır.
Herhangi bir implementasyon bu operasyonları destekleme seçeneğine sahiptir. Dilerse hiçbirini de desteklemeyebilir. Desteklenmeyen bir operasyon çağırılırsa UnsupportedOperationException fırlatılır.

Core collection interface'leri şunlardır:

1- Collection
Collection hiyerarşisinin tepe noktasıdır.
Element adı verilen bit grup nesneyi temsil eder.
Collection interface'i bütün collectionların ortak paydası olduğundan genelde collectionları göndermek ya da genel olarak manipüle etmek için kullanılır.
Bazı collectionlarda aynı elemandan birden fazla bulunabilir, bazılarında bulunamaz.
Bazıları sıralı, bazıları sırasızdır.
Direk implementasyonu yoktur; fakat alt-interface'lerinin direk implementasyonları vardır, örneğin Set ve List gibi.

Collection'a bir örnek olarak:

Collection col = new ArrayList();
col.add("ankara");
col.add(123);
col.add(true);
System.out.println(col);

output:
[a, 1, true]

2- Set
Birden fazla aynı elemanı barındıramaz. Matematikteki küme ifadesinin modelidir.
Bir poker elindeki kağıtlar, bir öğrencinin aldığı dersler, vb.

Set'e örnek olarak:

Set<Integer> tags = new HashSet<>();
tags.add(12);
tags.add(24);
tags.add(48);
tags.add(96);
tags.add(96);
System.out.println(tags);

output:
[48, 96, 24, 12]

3- List
Sıralı bir collectiondır (sequence da denir). Birden fazla aynı elemandan barındırabilir. Kullanıcı index pozisyonlarına göre elemanlara erişebilir.

4- Queue
Bir grup elemanı işlemeden önce elde tutmaya yarar.
Temel Collection operasyonlarının yanında bazı ekstra ekleme, çıkarma, inceleme operasyonları bulunur.
Genellikle elemanları FIFO şeklinde sıralar. Bu kurala uymayan bir örnek priority queue'lardır. Bunlar elemanları verilen bir comparatora göre ya da elemanların doğal sırasına göre sıralar.
Sıralama nasıl olursa olsun, sıranın başındaki eleman ilk olarak atılacaktır (remove ya da poll metodu ile).  FIFO queue'da yeni elemanlar sıranın sonuna eklenir. Diğer queue'larda farklı yerleştirmeler yapılabilir.
Her bir Queue implementasyonu kendi sıralama şeklini belirtmek zorundadır.

5- Deque
İşlem yapmadan önce bir grup elemanı tutmaya yarar. Queue'da olduğu gibi ekstra operasyonları vardır. FIFO ya da LIFO şeklinde kullanılabilir. Yeni elemanlar her iki uca da eklenebilir ya da çıkarılabilir.

6- Map
Bir grup key'i belirli value yani değerlere map'ler, eşleştirme yapar.
Bir mapte aynı key'den birden fazla bulunamaz; bir key birden fazla value'ya map edilemez.

7- SortedSet
Set interface'inin sort edilmiş versiyonudur.
Elemanlar artarak sıralanır.
Genellikle doğası gereği sıralı olan kümeleri, örneğin kelime listeleri ya da üyelik kayıtları gibi grupları modellemekte kullanılır.

8- SortedMap
Map interface'inin sort edilmiş versiyonudur.
Key'ler artarak sıralanır.
Genellikle doğası gereği sıralı olan key-value çiftleri için kullanılır: örneğin sözlük ya da telefon rehberi gibi.


The Collection Interface


Bütün Collection implementasyonlarında argümanı Collection olan bir constructor vardır.
Buna conversion constructor denir. Bu constructor verilen bir collection'ın tipini convert ederek, içindeki elemanlarla yeni bir collection yaratır.

Örneğin List, Set ya da başka tipte bir String collectionı "c" olduğunu farzedelim.
Bu collectiondaki bütün elemanları alıp yeni bir ArrayList yaratmak için:
List list = new ArrayList(c);
...
JDK 7 ve üzeri için diamond operatörü kullanabiliriz:
List list = new ArrayList<>(c);
...

Collection interface'de bazı temel operasyonları gerçekleştiren metodlar bulunur:
 int size(),boolean isEmpty()boolean contains(Object element)boolean add(E element)boolean remove(Object element), Iterator iterator().
...
Bunun yanında bütün bir collection üzerinde yapılan işlemler de vardır:
boolean containsAll(Collection c)boolean addAll(Collection c),boolean removeAll(Collection c)boolean retainAll(Collection c), and void clear().
...

Array işlemleri için bazı metodlar da vardır:
Object[] toArray(),  T[] toArray(T[] a
..

JDK 8 ve üzerinde Collection interface'i collection'larda stream şeklinde nesne alan metodlar bulunur:
 Stream stream() , Stream parallelStream()
...

Collection interface metodları bir collection'daki;
- eleman sayısını gösterir: size , isEmpty
- bir elemanın olup olmadığını gösterir : contains
- eleman ekleme  ve çıkarma işlemlerini yapar : add, remove
- elemanlar üzerinde gezinebilen bir iterator sağlar : iterator

add metodu aynı elemandan birden fazla barındırılmasına müsade eden ve etmeyen tiplere uyması için genel olarak tasarlanmıştır.
add metodu, metod çağrısı tamamlandığında verilen elemanın o collectiona eklendiğini garanti eder. Eğer metod bitiminde collectionda değişiklik olduysa true döndürür.

Aynı şekilde remove metodu da belirtilen elemanın tek bir intance'ının çıkarılmasını sağlar. İşlem sonunda collection değiştiyse true döndürür.

Traversing collections


Bir collectionda gezinmenin üç farklı yolu vardır: aggregate operasyonlar , for-each yapısı ve Iterator'lar.

Aggregate Operations


JDK 8 ve üzerinde bir collectionda gezinmenin tercih edilen yöntemi bir stream almaktır. Bu stream üzerinde aggregate operasyonlar yapılır.
Aggregate operasyonlar genelde lambda ifadeleriyle daha kısa ve öz biçimde yazılır.

Diyelim ki çeşitli isimler içeren bir collection'da sırayla ilerlemek ve listedeki elemanlar içinden sadece erkeklerin isimlerini ekrana yazdırmak istiyoruz .
Bu işlemi stream alarak ve lambda ifadesi kullanarak tek satırda yapabiliriz:

isimCollection.stream()
.filter(e -&gt; e.getGender() == Gender.MALE)
.forEach(e -&gt; System.out.println(e.getName()));
...

Aynı şekilde paralel bir stream alarak çok-çekirdekli mimarinin paralel işlem yapma potansiyelinden de yararlanabiliriz.
isimCollection.parallelStream()
.filter(e -&gt; e.getGender() == Gender.MALE)
.forEach(e -&gt; System.out.println(e.getName()));
...

Bu API ile verileri çok çeşitli yollardan listeleyebiliyoruz.
Diyelim ki bir collection'ın elemanlarını String nesnelerine çevirmek istiyoruz.
Sonra bunları birleştirip aralarına virgül koyacağız.

 String joined = elements.stream()
    .map(Object::toString)
    .collect(Collectors.joining(", "));
...

Diyelim ki bütün çalışanların maaşlarının toplamını almak istiyoruz:

int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
...

Aggregate operasyonlar hakkında daha detaylı bilgi için:
https://docs.oracle.com/javase/tutorial/collections/streams/index.html
...


Not: Collections framework API'de bulk operations yani toplu işlemler de vardır.
Bunlar bir collectionın bütünü üzerinde işlem yaparlar, örneğin containsAll, addAll, removeAll, vb.
Bunları aggregate işlemlerle karıştırmamak gerekir.
İkisi arasındaki farkı belirtmek gerekirse: eski yöntem olan bulk operasyonlar mutative yani değişiklik yapan işlemlerdir. Yeni yöntem olan aggregate işlemler ise collectionda hiçbir değişiklik yapmazlar.
Aggregate işlemleri ve lambda ifadelerini kullanırken mutation yani değişiklik yapmaktan kaçınmamız gerekir. Aksi halde ileride paralel bir streamden çalıştırıldığında bu kod sorun yaratacaktır.

for-each yapısı


Bir for loop içerisinde bir collection ya da array üzerinde gezinebiliriz.
Diyelim ki bir collectiondaki tüm objeleri ekrana satır satır yazdırmak için:
for (Object o : collection)
    System.out.println(o);
...

Iterators


Iterator ile bir collectionda gezinebilir ve belirli elemanları seçerek çıkartabiliriz.
Bir collection'ın iterator() metodunu kullanarak o collection üzerinde çalışacak bir Iterator alabiliriz.
Iterator interface'i şöyle tanımlıdır:

public interface Iterator {
    boolean hasNext();
    E next();
    void remove(); //optional
}
...
Buradaki hasNext() metodu, eğer iterasyonda ilerlenebilecek başka elemanlar varsa true döndürür.
next() metodu iterasyonda sıradaki elemanı döndürür.
remove() metodu next() ile döndürülen en son elemanı çıkartır.
remove metodu bir next çağrısında sadece tek bir kez çağırılabilir. Aksi halde exception fırlatır.

Not: Bir collectionı iterasyon sırasında değiştirmek için güvenli olan tek yol Iterator.remove metodunu kullanmaktır.
Aksi halde java.util.ConcurrentModificationException hatası alınabilir.
Collection'daki remove() metodu fail-fast bir metoddur.
Yani hataya sebep olabilecek durumlarda hemen hataya düşerek ileride sorun çıkmasının önünü almak ister.
Örneğin aşağıdaki kod exception verecektir:

Iterator iter = list.iterator();
        while (iter.hasNext()) {
            String s = (String) iter.next();
            if (s.equals("hello")) {
                list.remove(s);
            }
        }
...
Bunu düzeltmek için List.remove yerine Iterator.remove metodunu kullanmak gerekir. Şimdi hata vermeyecektir:

while (iter.hasNext()) {
            String s = (String) iter.next();
            if (s.equals("hello")) {
                iter.remove();
            }
        }

...


Hangi  durumlarda for-each yerine Iterator tercih edilmelidir?

- Current element silinecekse.
For-each yöntemi iteratorı gizlediğinden remove metodunu çağıramayız. Bu yüzden for-each yapısı filtreleme için uygun değildir.
- Paralel olarak birden fazla collectionda iterasyon yapılması gerekiyorsa.


Bir collectionı filtreleyerek belirli elemanları silmek için şu yöntem uygulanır:
static void filter(Collection c) {
    for (Iterator it = c.iterator(); it.hasNext(); )
        if (!cond(it.next()))
            it.remove();
}
...
Burada polymorphic bir metod yazılmıştır. Hangi collection implementasyonu olursa olsun, hepsinde çalışacaktır.
Java Collections Framework ile bu şekilde polymorphic algoritmaları kolayca yazabiliriz.

Collection Interface Bulk Operations


Bulk yani toplu işlemler bir collectionın bütünü üzerinden yapılır.
-containsAll : verilen collectiondaki tüm elemanlar hedefte bulunuyorsa true döndürür
-addAll         : verilen collectiondaki bütün elemanları hedefe ekler
-removeAll   : hedefteki collectiondan verilen collectiondaki tüm elemanları siler
-retainAll      : hedefteki collectiondan verilen collectionda olmayan tüm elemanları siler
-clear            : verilen collectiondaki bütün elemanları siler

addAll, removeAll ve retainAll metodları eğer hedefte değişiklik olmuşsa true döndürürler.

Diyelim ki bir collectiondan belirli bir e elemanının bütün instance'larını silmek istiyoruz.

c.removeAll(Collections.singleton(e));

Ya da bütün null elemanları silelim:

c.removeAll(Collections.singleton(null));

Burada Collections.singleton static factory metodu kullanılmış. Bu metod immutable bir Set döndürür; bu sette sadece belirtilen eleman bulunur.

Collections Interface Array Operations

Collectionlar ile parametre olarak eski tip array isteyen eski API'ler arasında geçiş yapabilmek için toArray metodları kullanılır.
Bu metodlar bir collectiondaki bütün elemanları bir array içinde toplar.
Argüman verilemzse yeni bir Object arrayi yaratır.
Diğer versiyonlarında bir array verebilir ya da arrayin runtime tipini belirtebiliriz.

Diyelim ki elimizde bir c Collectionı olsun. c'nin bütün elemanlarını yeni bir Object arrayine atalım.

Object[] a = c.toArray();

Diyelim ki c sadece String lerden oluşsun. c'nin elemanlarını bir String array'ine aktaralım.

String[] a = c.toArray(new String[0]);


Java Collections : The Set Interface

Set, aynı elemandan birden fazla barındıramayan bir Collection türüdür.
Set interface'de sadece Collection sınıfından miras aldığı metodlar vardır.
Bu metodlara aynı elemandan birden fazla bulundurmama şartını ilave etmiştir.
İki setin eşit olması için tamamen aynı aynı elemanları içermeleri gerekir.

Java platformu Set interface'inin  HashSet, TreeSet ve LinkedHashSet implementasyonlarını bulundurur.

1. HashSet

Tanımı:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable

Elemanlarını hash table'da barındırır.
En performanslı olan set implementasyonu budur.

2. TreeSet  

Tanımı:
public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable

Elemanlarını red-black tree'de barındırır.
Değerlere göre sıralama yapar. 
Performansı daha düşüktür.


3. LinkedHashSet : 
SortedSet interface'ini implement eder.
HashSet'in alt sınıfıdır.
Linked list içeren bir hash table'dan ibarettir.
elemanları eklenme sırasına göre sıralar. 
Bir setin sıralama yapmasını, fakat HashSet gibi de performanslı olmasını istediğimiz zaman kullanabiliriz.
Performansı HashSet'e göre çok az düşüktür.


Örnekler

Diyelim ki elimizde bir Collection olan "sehirler" var ve bunun içindeki kopyaları (aynı elemandan birden fazla olanları) silmek istiyoruz.


ArrayList<String> sehirler = new ArrayList<>();
sehirler.add("istanbul");
sehirler.add("ankara");
sehirler.add("izmir");
sehirler.add("istanbul");
sehirler.add("ankara");
Collection<String> tekSehirler = new HashSet<String>(sehirler);
output: [ankara, izmir, istanbul]
...
Burada tek satırda c'yi bir HashSet'e dönüştürerek conversion constructor aracılığıyla kopyalardan da kurtulmuş oluyoruz.
Sıranın değişmesini istemiyorsak HashSet yerine LinkedHashSet kullanabiliriz.

JDK 8+ için aggregate operasyonları kullanarak dönüştürebiliriz:


Set s = cities.stream().collect(Collectors.toSet());
System.out.println(s);
output: [ankara, izmir, istanbul]
...
Ya da bir TreeSet' e dönüştürebiliriz:


Set<String> set = cities.stream()
                .map(Object::toString)
                .collect(Collectors.toCollection(TreeSet::new));        
System.out.println(set);
output: [ankara, istanbul, izmir]
...


18 Nisan 2015 Cumartesi

Java ve Concurrency (=Threads)

https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html

Bir uygulama birden fazla şeyi aynı anda yapabiliyorsa buna concurrent uygulama denir. Concurrency kavramı aynı anda birden fazla işlem yapabilme yeteneğini ifade eder.
java.util.concurrent paketi bu amaçla kullanılır.


Processes and Threads


Concurrent programlamada iki tane çalışma birimi vardır: process ve thread.
Java'da concurrency thread'lerle sağlanır.

Bir tek işlemci çekirdeğinde dahi birden fazla process ve thread zaman paylaşımı yaparak aynı anda çalışma izlenimi uyandırır. Buna işletim sistemi terminolojisinde time slicing denir.

Process

Her processin kapalı bir çalışma ortamı, yani kendine özel olarak ayrılan bir bellek alanı vardır.
Kullanıcının tek bir uygulama olarak gördüğü şey aslında birden fazla processin birlikte çalışmasının ürünü olabilir. İşletim sistemi processler arası iletişimi sağlamak için Inter-Process Communication (IPC) kaynaklarını, yani pipe, socket,vb kullanır. IPC aynı zamanda farklı sistemlerdeki processlerin de iletişimi sağlar.

JVM implementasyonları çoğunlukla tek processten oluşur. ProcessBuilder nesnesi ile bir jva uygulamasına processler eklenebilir.

Thread

Hem processler hem de threadler, ikisi de çalışma ortamı sağlarlar. Fakat yeni bir thread yaratmak için gereken kaynaklar daha azdır. Bu yüzden threadlere hafif processler de denir.

Threadler processlerin içinde varolurlar. Her processte en az bir tane thread vardır.
Bir processin kaynaklarını (örneğin bellek alanı ve açılmış olan dosyalar gibi) threadler ortaklaşa kullanırlar. Bu yüzden çok hesaplı fakat bazen de problemli bir iletişimleri vardır.

Her processte ilk olarak tek bir main thread bulunur. Bu threadi kullanarak yeni threadler yaratabiliriz.

Thread objects


Her thread, Thread classından yaratılan bir instance'a bağlıdır.
Thread kullanarak multi-threaded uygulama yapmanın iki yöntemi vardır:
1. Threadleri direkt olarak yaratarak yönetimini yapmak
Yani uygulamanın asenkron bir işlem yapması gerektiğinde, her defasında Thread classından yeni bir instance almak.
2. Threadlerin yönetimini uygulamadan soyutlamak
Yani uygulamanın asenkron işlemlerini bir executor'a paslamak. (Java 5.0)

Thread tanımlamak ve çalıştırmak

Bir uygulama Thread nesnesi yaratacaksa, threadin çalıştıracağı kodu da mutlaka belirtmelidir.
Bunu yapmanın iki yolu vardır:

1. Runnable interface

Runnable interface'ini implement edebiliriz.

Runnable interface'i tek bir run() metodu tanımlar. Yaratılan runnable implementasyonu bir Thread'in constructor'ında argüman olarak verilir.

Runnable.java'yı açıp baktığımızda çok basit olduğunu görüyoruz (commentlerden arındırılmıştır):
...
package java.lang;

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

...

Runnable.java dosyasındaki commentleri okuduğumuzda bu interface'in yaratılmasındaki amacı anlayabiliriz.
Burada özellikle sadece run() metodu çalıştırılacaksa, Thread classını extend etmek yerine bu interface'i kullanmamız öneriliyor.
Çünkü bir classın davranışını değiştirmek ya da geliştirmek gibi bir niyetiniz yoksa, o classı extend etmeniz yanlış olur.
Eğer ki sadece bir Thread instance'ın run() metodunu kullanmamız gerekiyorsa, o zaman bütün bir Thread classını extend etmeye gerek yoktur.
Özellikle ileride başka bir classı extend etmek gerektiğinde de engel teşkil etmemesi açısından, daima interface'e göre kodlama yapmak ("program to an interface, not an implementation" prensibi) doğru bir yaklaşım olacaktır.

Örnek:

public class HelloRunnable implements Runnable {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }

}

...

2. Thread class

Thread classından kalıtım alabiliriz.

Thread classı Runnable interface'ini implement eder. Fakat run() metodu boştur.
Bir uygulama Thread'i extend ederek bir alt sınıf yaratıp, kendi run() metodu implementasyonunu yapabilir.

Thread.java dosyasını açıp baktığımızda oldukça uzun bir dosya olduğunu görüyoruz. Bir kısmını gözardı edersek özeti şu şekilde kalır:
...
public class Thread implements Runnable {
   
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;

    public static void sleep(long millis, int nanos)   throws InterruptedException {    }
 
    public Thread() {  init(null, null, "Thread-" + nextThreadNum(), 0);    }
    public Thread(Runnable target) {  init(null, target, "Thread-" + nextThreadNum(), 0);    }

    public synchronized void start() {   }   
    @Override
    public void run() { if (target != null) { target.run(); }}     
 
    @Deprecated
    public final void stop() {    }   
    @Deprecated
    public final synchronized void stop(Throwable obj) { }
 
    public void interrupt() { }
 
    @Deprecated
    public void destroy() {  }    
    @Deprecated
    public final void suspend() {    }   
    @Deprecated
    public final void resume() {    }
   
    public final synchronized void join(long millis)    throws InterruptedException { }  
    public final void join() throws InterruptedException {        join(0);    }

    public static void dumpStack() {        new Exception("Stack trace").printStackTrace();    }

    public final void setDaemon(boolean on) {  }  
    public final boolean isDaemon() {  }  

    public final void checkAccess() {  }
    public static native boolean holdsLock(Object obj);    
    public enum State {  
        NEW,    
        RUNNABLE,    
        BLOCKED,    
        WAITING,      
        TIMED_WAITING,    
        TERMINATED;
    }      
}

...
Bütün bunların arasında önemli noktalar şunlardır:

* Thread classı Runnable interface'ini implement eder. run() metodunu override eder.

* Yeni thread instance yaratılırken runnable nesnesi parametre olarak verilebilir.

* start() metodu ile çalıştırılırlar ve bu metod synchronized modifier'ı ile işaretlenmiştir

* sleep() metodu ile milisaniye (saniyenin binde biri) cinsinden bekletilirler

* thread'lerin priority yani öncelikleri vardır.

* Ayrıca Thread'lerin state'leri vardır:
  public enum State {  
        NEW,    
        RUNNABLE,    
        BLOCKED,    
        WAITING,      
        TIMED_WAITING,    
        TERMINATED;
    }

* stop(), destroy(), suspend(), resume() metodları Deprecated yani kullanımdan kalkmıştır.


Kısacası Thread sınıfını extend ederek run metoduna kendi implementasyonumuzu yazdığımız zaman, artık yeni aldığımız instance'ları start() metodu ile çalıştırabiliriz.

Örnek:
public class HelloThread extends Thread {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new HelloThread()).start();
    }

}
...
Hangisini tercih etmeli?

Bu iki yaklaşımdan birincisi, yani Runnable interface'ini implement etmek hem daha esnek bir yaklasım oluyor, hem de high-level thread management API'de de kullanılabiliyor.
İkinci yöntem daha basit kullanımı olmasına rağmen, gereksiz yere subclassing yapıldığından genelde önerilmiyor.

Thread classı thread yönetimi için bir çok faydalı metod sağlıyor. Statik metodlar metodu çağıran thread hakkında bilgi verebiliyor.

sleep metodu ile çalışmayı duraklatmak


Thread.sleep() metodu threadin çalışmasını belirli bir süre boyunca duraklatır.
Bu sayede o anda çalışmakta olan diğer threadler de işlemciden yararlanabilirler.
sleep() metodu aynı zamanda threade zaman aralıkları verilmesini ve bir threadin diğerinin işlemini bitirmesini beklemesini sağlar.

sleep() metodunun iki tane overloaded versiyonu mevcuttur: biri milisaniye, diğer ise nanosaniye cinsinden bekleme süresini parametre alır.
Fakat bu sürelerin kesin olmayacağını unutmamak gerekiyor. Çünkü bu zamanlamaların ayarlanması alttaki işletim sistemine bağlıdır ve interruptlar ile de zamanlamanın bölünmesi mümkündür.
Kısacası sleep metodunun threadi kesin olarak verilen miktarda bekleteceğinin garantisi yoktur.

Örneğin aşağıdaki program Thread.sleep() metodundan yararlanarak 4 saniyelik aralıklarla ekrana yeni bir mesaj yazıyor:

public class SleepMessages {
    public static void main(String args[]) throws InterruptedException {
        String info[] = {"Mares","Does","Lambs","ivy"};

        for (int i = 0; i < importantInfo.length; i++) {            
            Thread.sleep(4000);            
            System.out.println(info[i]);
        }
    }
}
...

Buradaki InterruptedException, eğer bu thread sleep durumunda iken başka thread tarafından interrupt edilirse ortaya çıkar.
Bu örnekte bu threadi interrupt edebilecek başka bir thread tanımlanmadığından, exceptionı yakalamak için bir şey yazmaya gerek yok.

Interrupts


Interrupt bir threadin yaptığı işi bırakıp başka bir şeye geçmesi gerektiğinin işaret edilmesidir.
Genelde interrupt edilen thread terminate eder (fakat başka davranışlar verdirilmesi de mümkündür).
Bir thread diğerini interrupt edebilmek için o instance'ın interrupt() metodunu çağırır.
interrupt mekanizmasının doğru çalışması için interrupt edilen threadin kendi interruptını desteklemesi gereklidir.

Bir threadin kendi interrupt'ını desteklemesi
Genel davranış, interrupt geldiğinde run() metodundan hemen return etmektir.
Örneğin SleepMessages classına interrupt desteği vermek için exceptionı yakaladığımız yere return ifadesini eklememiz yeterlidir:

for (int i = 0; i < importantInfo.length; i++) {
    // Pause for 4 seconds
    try {
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        // We've been interrupted: no more messages.
        return;
    }
    // Print a message
    System.out.println(importantInfo[i]);
}
...
sleep() gibi InterruptedException veren metodlar genellikle hemen yaptığı işi iptal edip return etmek üzere tasarlanmıştır.
Örnek:

for (int i = 0; i < inputs.length; i++) {
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
        // We've been interrupted: no more crunching.
        return;
    }
}
...
Eğer kodun uzun bir kısmında InterruptedException veren bir metod yoksa, bu durumda düzenli olarak interrupt gelip gelmediğini kontrol etmesi gerekir. Bunun için boolean döndüren  Thread.interrupted() metodunu kullanabiliriz:

for (int i = 0; i < inputs.length; i++) {
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
        // We've been interrupted: no more crunching.
        return;
    }
}
...
Yukarıdaki gibi basit olmayan daha kompleks uygulamalarda ise interrupt alındığı zaman bir exception throw edilmesi mantıklı olur. Böylece exception handling kodları tek bir catch içerisinde toplanabilir:

if (Thread.interrupted()) {
    throw new InterruptedException();
}
...

interrupt status flag

interrupt mekanizması, interrupt status adlı bir flag aracılığıyla gerçekleşir. Thread.interrupt() metoduset edildiğinde interrupt edilmiş olur. Bir thread, Thread.interrupted() statik metodu ile interrupt edilip edilmediğini kontrol ettiğinde interrupt status sıfırlanır.
Statik olmayan isInterrupted() metodu ile başka bir threadin interrupt durumunu öğrenebiliriz. Bu metod interrupt status flagine dokunmaz.

Genellikle InterruptedException throw eden bir metod return ettiği zaman interrupt status flagini de sıfırlar. Fakat bunun hemen arkasından başka bir threadin interrupt metodunu çağırmasıyla interrupt status flaginin tekrar set edilmesi mümkündür.

Joins


Join bir threadin diğerinin işini bitirmesini beklemesi anlamına gelir.
Bir t threadi o anda çalışmakta ise bulunduğumuz yere t.join() yazarsak current thread pause moduna geçerek, t'nin bitmesini bekleyecektir.
join() metodunun overloaded versiyonları ile belirli zaman aralıkları verebiliriz.
Yalnız sleep() metodunda olduğu gibi join() metodunda da kesin olarak verilen süre kadar bekleneceğinin garantisi yoktur.

sleep() metodu gibi join() metodu da interrupt edildiğinde InterruptedException fırlatarak exit eder.

Örnek:
SimpleThreads classında iki thread bulunuyor. Eğer MessageLoop threadi gereğinden uzun sürerse, main thread onu interrupt ediyor. Interrupt edilen thread ekrana henüz işinin bitmediği bilgisini yazıp exit ediyor. Bu esnada main thread onun yokolmasını join() ile bekliyor.

public class SimpleThreads {

    // Display a message, preceded by
    // the name of the current thread
    static void threadMessage(String message) {
        String threadName =
            Thread.currentThread().getName();
        System.out.format("%s: %s%n",
                          threadName,
                          message);
    }

    private static class MessageLoop
        implements Runnable {
        public void run() {
            String importantInfo[] = {
                "Mares eat oats",
                "Does eat oats",
                "Little lambs eat ivy",
                "A kid will eat ivy too"
            };
            try {
                for (int i = 0;
                     i < importantInfo.length;
                     i++) {
                    // Pause for 4 seconds
                    Thread.sleep(4000);
                    // Print a message
                    threadMessage(importantInfo[i]);
                }
            } catch (InterruptedException e) {
                threadMessage("I wasn't done!");
            }
        }
    }

    public static void main(String args[])
        throws InterruptedException {

        // Delay, in milliseconds before
        // we interrupt MessageLoop
        // thread (default one hour).
        long patience = 1000 * 60 * 60;

        // If command line argument
        // present, gives patience
        // in seconds.
        if (args.length > 0) {
            try {
                patience = Long.parseLong(args[0]) * 1000;
            } catch (NumberFormatException e) {
                System.err.println("Argument must be an integer.");
                System.exit(1);
            }
        }

        threadMessage("Starting MessageLoop thread");
        long startTime = System.currentTimeMillis();
        Thread t = new Thread(new MessageLoop());
        t.start();

        threadMessage("Waiting for MessageLoop thread to finish");
        // loop until MessageLoop
        // thread exits
        while (t.isAlive()) {
            threadMessage("Still waiting...");
            // Wait maximum of 1 second
            // for MessageLoop thread
            // to finish.
            t.join(1000);
            if (((System.currentTimeMillis() - startTime) > patience)
                  && t.isAlive()) {
                threadMessage("Tired of waiting!");
                t.interrupt();
                // Shouldn't be long now
                // -- wait indefinitely
                t.join();
            }
        }
        threadMessage("Finally!");
    }
}
...

Synchronization


Threadler yukarıdaki örnekte kaynakları ortaklaşa kullanarak iletişim kuruyorlar. Aynı primitif ya da nesnelere erişebiliyorlar.
Bu şekilde yapılan iletişim çok etkili olsa da, thread interferance ve memory consistency hatalarına sebep olabiliyor.
Bu hatalardan korunmak için synchronization yapılması gerekiyor.
Yalnız synchronization yapıldığında da thread contention yani birden fazla threadin aynı kaynağa aynı anda erişmeye çalışılması problemi ortaya çıkıyor.
Bu durumda java runtime bazı threadleri yavaşlatıp bazen tamamen durdurabiliyor.
Thread contention örnekleri olarak starvation ve livelock verilebilir.

Thread interferance


Bazen bir kaynağa aynı anda erişen birden fazla thread işlem yaparsa farklı sonuçlar oluşur.
Bu durumda threadlerden biri diğerlerinin elindeki sonucun üzerine yazar ve sonuçlarsa tutarsızlık ortaya çıkar. Buna thread interferance yani threadlerin birbirine karışması denir.

Örneğin aşağıdaki counter sınıfından bir instance alınır ve bu instance üzerinde birden fazla thread aynı anda işlem yaparsa, sonuçlar beklenenden farklı çıkabilir.

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}
...
Çünkü ++ ve -- işlemleri göründüğünün aksine atomik işlemler değildir. Yani birden fazla işlemden oluşur.
Örneğin c++ işlemi aslında
1) c'nin okunması
2) alınan değere bir eklenmesi
3) bulunan sonucun tekrar c'ye yazılması
şeklinde 3 aşamadan oluşur.

Böyle durumların olup olmayacağı önceden tahmin edilemediğinden, bazen de doğru sonuçlar verdiği için farkedilmesi çok zordur.

Memory consistency errors


Memory consistency hatası, birden fazla threadin aynı görmeleri gereken bir değeri farklı görmeleridir.
Bir işlemin mutlaka diğeri başlamadan önce bitmesi gerektiğini ifade etmek için happens-before relationship kavramı kullanılır. Happens-before ilişkisi kurulmadan yapılan işlemler, birbirinden habersiz olduklarından tutarsız sonuçlara neden olur.

Thread.start() ve Thread.join() metodları happens-before ilişkileri kurabilirler.
Aynı şekilde synchronized ve volatile keywordleri ile de happens-before ilişkileri kurulabilir.

Synchronized methods


Java'da senkronlama işlemi için synchronized method ve synchronized statement'lar kullanılır.

Bir metodu senkron hale getirmek için declaration'ın başında synchronized keywordu koymak yeterlidir.

Örnek:

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}
...
Buradaki metodları senkron hale getirdiğimizde şunları sağlamış oluruz:
1- Aynı instance'ın aynı metodunun birden fazla thread tarafından aynı anda çağırılması imkansızdır.
Yani bir thread o nesnenin senkron bir metodunu çağırdığında, aynı nesnenin aynı metodunu çağıran tüm diğer threadler birinci threadin işi bitene kadar bloklanır, yani çalışmayı durdururlar.

2- Bir senkron metodun işi bittiğinde hemen sonraki metod çağrıları ile arasında otomatik olarak happens-before ilişkisi kurulur. Yani nesnenin durumunda (state) yapılan değişiklikler anında bu metodu çağıran diğer threadlere de görünür.

Not: Constructor'lar senkron yapılamaz. Eğer bir constructor'a synchronized keywodu eklenirse compiler hata verir. Bunun sebebi bir nesneyi yaratan constructora sadece onu çağıran threadin  erişebilir olmasıdır. Yani senkronlamaya gerek yoktur.

Uyarı: bir classın bütün instance'larını bir listede tutmak isteyebiliriz. Fakat bunu yaparken nesnenin yaratılması bitmeden referansının diğer threadler tarafından çağırılmamasına dikkat etmek gerekir.
Örneğin
instances.add(this);
...
Yukarıdaki satırı constructor'a eklersek, diğer threadler instances adlı liste aracılığıyla daha yaratılmadan bu nesneye erişmeye çalışabilir.

Senkron metodlar sayesinde thread interferance ve memory inconsistency hatalarına karşı gereken önlemler alınmış olur. Eğer bir nesne birden fazla threade görünüyorsa o nesnenin variable'larına yapılan bütün okuma ve yazma işlemleri synchronized metodlar aracılığıyla yapılır.

Not: final keywordu ile işaretlenen field'lar bir kez initialize edildikten sonra asla değiştirilemezler. Bu yüzden senkron olmayan normal metodlarla güvenli şekilde okunabilirler.
Yalnız senkron olması istenen alanlar final ile işaretlenirse, liveness adı verilen bir problem ortaya çıkabilmektedir.

Intrinsic locks and synchronization


Synchronization işlemi, instrinsic lock ya da monitor lock (API spec. de sadece monitor olarak geçer) adı verilen internal bir entity ile sağlanır.
Bu lock yani kilitler bir nesnenin state'ine exclusive (tekil) erişim sağlanması ve happens-before ilişkilerinin kurulmasında hayati önem taşır.

Her nesnenin bir kilidi (lock) vardır. Threadler bir nesnenin field'larına erişmek için önce kilidi ele geçirmek zorundadır. Kilide erişimi elde ettiğinde işi bitene kadar nesneyi kilitler; işi bittiğinde kilidi açar (release).
Bir thread bir nesnenin kilidini ele geçirdiğinde o kilide sahip olduğunu söyleriz (owns the lock). Kilit serbest bırakılana kadar diğer threadler o kilide erişemezler.
Erişmeye çalışan bütün threadler bloklanır.

Kilit serbest bırakıldığında bir sonraki ele geçirme işlemi ile arasında happens-before ilişkisi kurulur.

Locks in synchronized methods

Bir thread bir nesnenin synchronized metodunu çağırdığı anda o nesnenin kilidini ele geçirir. Metod normal şekilde ya da exception ile return ettiği anda kilit açılır.

Not: static bir metod synchronized olduğunda ne olur? static metodlar nesnelere değil classlara bağlıdır. Bu yüzden thread ilgili sınıfa bağlı olan Class nesnesinin kilidini ele geçirir.
Böylece ilgili classın static field'larına instance nesnelerinin kilitlerinden bağımsız olan, ayrı bir kilit aracılığıyla erişilmiş olur.

Synchronized statements

Senkronlamanın bir diğer yöntemi de senkron statement kullanmaktır. Yalnız senkron statement'larda senkronlanacak (yani dışarıdan erişime karşı kilitlenecek) olan nesnenin parantez içinde belirtilmesi gereklidir.

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}
...
Yukarıdaki addName() metodu ile lastName ve nameCount alanlarındaki değişimi senkronize etmek istiyoruz. Fakat aynı zamanda nameList nesnesinin add() metod çağrılarını bu kapsamda senkronlamak istemiyoruz (nesneye erişimi bloklamak istemiyoruz - senkron bir kod bloğu içinde başka nesnelerin metodları çağrılırsa Liveness problemleri ortaya çıkabilir).

Bu durumda addName metodunu synchronized keywordu ile senkron yapıp, içindeki nameList.add için senkron olmayan ayrı bir metod yazmak yerine; tek bir asenkron metod yazıp bu metodda senkronlamak istediğimiz alanları synchronized(this) {} bloğu içerisine hapsetmemiz yeterli oluyor.

Senkron bloklar aynı zamanda daha ince ayar gerektiren durumlarda da çok kullanışlıdır.
Örneğin birbirinden ayrı olarak okunup yazılan c1 ve c2 alanlarını senkronize etmek istiyoruz.
Fakat bunların herhangi birine erişileceği zaman diğerinin de otomatik olarak kilitlenmesini, ve onu kullanan threadlerin bloklanmasını istemiyoruz.
Bu durumda this keywordu ile bütün alanları kilitlemek yerine, bu alanlara özel kilit nesneleri yaratarak özelleştirilmiş kilitler ile senkronlama yaparız.

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}
...

Yukarıdaki örnekte lock1 ve lock2 adlı nesneler c1 ve c2 alanlarını ayrı ayrı senkronlayan kilit görevi görüyorlar. Bu sayede c1 değişkeni artırılacağı zaman c2 değerini artırmak isteyen bir threadin nesneye erişimi bloklanmıyor.

Not: Bu yöntemi uygularken bu iki alana farklı threadler tarafından aynı anda erişilmesinin hiçbir sakınca oluşturmadığından mutlaka emin olunması gerekiyor.

Reentrant synchronization

Bir thread başka bir threadin elinde bulunan kilide erişemez. Fakat kendi elinde bulunan bir kilide erişebilir. Bir threadin bir kilidi birden fazla kez elde etmesine reentrant synchronization denir.

Bunun olabilmesi için bir threadin senkronize bir kod bloğu içerisinde direk ya da dolaylı olarak yine senkron kod içeren bir metodu çağırması gerekir. Bu durumda iki kod bloğu da aynı kilidi kullanır.

Reentrant synchronization olmasaydı threadlerin kendi kendilerini bloklamaması için ekstra önlemler alınması gerekecekti.

Atomic access


Atomic işlem tek seferde yapılan işlem demektir. Atomic bir işlem yarıda kesilemez; ya tamamen gerçekleşir, ya da hiç gerçekleşmez. Atomic bir işlem bitene kadar sistemde hiç bir etkisi görülmez.

Increment operasyonunun (c++) atomic olmadığını biliyoruz. Bu gibi işlemler tek seferlik gibi görünse de aslında birden fazla işlemin birleşmesinden oluşmuştur.
Atomic işlemlere örnek vermek gerekirse:
1- Bütün referans değişkenleri ve çoğu primitif değişkenin (long ve double hariç hepsi) okunması ve yazılması işlemleri atomiktir.
2- volatile keywordu ile işaretlenen bütün değişkenlerin (long ve double dahil) okunması ve yazılması atomiktir.

Atomik işlemler tek seferde yapıldığından birbirine karışmazlar. Bu yüzden thread interferance problemi yaratmazlar. Fakat yine de memory consistency hataları olabileceğinden senkronlama ihtiyacı vardır.
Volatile keywordunu kullandığımız zaman üzerinde yazma yaptığımız değişkenin bir sonraki okunması işlemi ile happens-before ilişkisi kurulur. Yani volatile olan değişkene yapılan bir değişiklik diğer threadler tarafından yapılan okumalarda görünecektir.
Bu sayede memory consistency hatalarının ortaya çıkma riski büyük oranda düşecektir.

java.util.concurrent paketindeki bazı sınıflarda senkronlamaya gerek duymayan atomik metodlar bulunur.

Liveness


Bir concurrent yani çok threadli uygulamanın belirtilen zamanlamaya uygun çalışmasına liveness denir.
En yaygın liveness problemi deadlock problemidir. Diğer bazı liveness problemleri de starvation ve livelock'tır.

Deadlock


Deadlock birden fazla threadin birbirini bekleyerek sonsuza kadar bloklanmaları durumuna denir.

Örneğin bir kişinin diğerine selam vermek için eğildiği, ve nezaket icabı diğeri selamını alana kadar doğrulmadığı bir kültürü varsayalım. Bu kültürde iki kişinin aynı anda selam vermeleri halinde, ikisi de bir diğerinin selamını almasını sonsuza kadar bekleyecektir.
Kimse diğerinin selamını alamadığından doğrulamayacak, ve doğrulamadığı için de yine selam alamayacaktır. Durum böylece bir nevi kısırdöngüye yani deadlock'a dönüşecektir.

Örnek:

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void selamVer(Friend dost) {
            System.out.format("%s: %s" + "  bana selam verdi!%n", this.name, dost.getName());
            dost.selamAl(this);
        }
        public synchronized void selamAl(Friend dost) {
            System.out.format("%s: %s" + " selamımı aldı!%n", this.name, dost.getName());
        }
    }

    public static void main(String[] args) {
        final Friend ali = new Friend("Ali");
        final Friend veli = new Friend("Veli");
        new Thread(new Runnable() {
            public void run() { ali.selamVer(veli); }
        }).start();
        new Thread(new Runnable() {
            public void run() { veli.selamVer(ali); }
        }).start();
    }
}
...
Burada ali, veli'ye selam verdikten sonra veli'nin selamAl() metoduna erişmeye çalışıyor.
Fakat ali'nin veli'nin selamAl() metoduna erişmesi imkansız; çünkü veli nesnesi o esnada kendi selamVer() metodu içerisinde ali'nin selamAl() metoduna erişmeyi bekliyor.
Böylece ali veli'ye, veli de ali'ye kendi selamVer() metodları içinde bloklanmış vaziyette beklemede kaldıklarından erişemiyorlar. Ve deadlock oluşuyor.

Starvation and Livelock


Deadlock kadar sık görülmese de, concurrent yazılımla uğraşanların zaman zaman karşılaştığı problemlerdir.

Starvation

Starvation, bir threadin bir kaynağı elinden çok uzun süre bırakmaması yüzünden diğer threadlerin o kaynağa erişmeyi bekleyerek bloklanıp kalmasıdır.
Genellikle açgözlü threadler bu probleme sebep olurlar. Örneğin bir nesnenin senkronize bir metodunun bitmesi çok uzun sürerse, bu metodu sıklıkla çağıran bir metod diğerlerinin erişimini büyük ölçüde engelleyecektir.

Livelock

Bir thread genellikle başka bir threadin davranışına cevap olarak harekete geçer.
Eğer bu davranışı yapan thread de bir diğer thread'in davranışına cevap olarak çalıştıysa, livelock ortaya çıkabilir.
Deadlock'ta olduğu gibi livelock'ta da threadler ilerleme kaydedemezler.
Yalnız burada threadler bloklanmamıştır. Diğer threadlere cevap vermekten o kadar meşguldür ki kendi işini yapmaya zaman ayıramaz.

Livelock problemi bir koridorda birbiriyle karşılaşıp bir türlü diğerinin yanından geçemeyen iki kişinin durumuna benzer.
Örneğin Ali ile Veli karşılaşırlar. Ali, Veli'nin yanından geçebilmek için sağa adım atar. Veli de Ali'nin yanından geçebilmek için sola adım atar. İkisi de yine karşı karşıya olduklarından diğer tarafa birer adım atarlar. Fakat yine karşı karşıya kaldıklarından geçemezler..


Guarded blocks


Bir thread diğerinin yapacağı bir eylemin sonucunu bekliyorsa bunu sağlamak için boş bir while döngüsü koymak boş yere işlemcinin zamanından çalınmasına neden olur.
Örnek:

public void guardedJoy() {
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {}
    System.out.println("Joy has been achieved!");
}
...

Bunun yerine Object.wait() metodunu kullanarak threadi bekletirsek, işlemcinin zamanından çalmamış oluruz. Buradaki wait() metodu diğer bir threadden herhangi bir notification alana kadar bekleyecektir.
Örnek:

public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}
...
wait() metodu çağırıldığında thread elindeki kilidi bırakacak ve beklemeye geçecektir.
Daha sonra diğer bir thread o nesnenin kilidini ele geçirip notifyAll() metodunu çağırırsa, bu nesne üzerindeki kilidi bekleyen bütün threadlere notification gönderilecektir.
Bildirimi yapan bu thread artık kilidi bırakacaktır.
Birinci thread bu notification'ı aldıktan sonra, ikincinin bıraktığı kilidi tekrar ele geçirdiği anda, wait işlemini bitirerek kaldığı yerden çalışmasına devam edecektir.


16 Nisan 2015 Perşembe

Java'da garbage collection

Garbage collection nedir?

Java'da garbage demek, referansı olmayan nesne demektir.
Garbage collection da runtime sırasında kullanılmayan bellek alanlarını otomatik olarak boşaltıp yeniden kullanılabilir hale getirmek demektir.
Yani artık kullanılmayan nesneleri yok etmektir.

Bunun için C dilinde free(), C++ dilinde delete() metodları kullanılır.
Java dilinde bu işlem otomatik olarak JVM'deki garbage collector tarafından halledilir.

Garbage collection'ın avantajı nedir?

Heap alanını otomatik olarak temizlediğinden etkin bir bellek yönetimi sağlar.
Kullanıcının nesneleri yok etmek ve bellekte yer açmak için efor sarfetmesine gerek kalmaz.

Bir nesne ne zaman kullanılmaz hale gelir?

1. Referansı null olduğunda:

Örneğin aşağıdaki örnekte "s" referansı ile yaratılan nesne referansı null yapıldığı anda garbage olmuştur.

Student s = new Student("a");
s=null;

2. Referansı başkasına yönlendirildiğinde:

Örneğin aşağıdaki örnekte "s1" referansı ile yaratılan nesne, referansı "s2" ile işaret edilen nesneye yönlendirildiğinde anda garbage olmuştur.

Student s1 = new Student("a");
Student s2 = new Student("b");
s1 = s2;

3. Anonim nesne şeklinde yaratılırsa:

new Student("a");





finalize() method

The finalize() method is invoked each time before the object is garbage collected. This method can be used to perform cleanup processing. This method is defined in Object class as:
  1. protected void finalize(){}  

Note: The Garbage collector of JVM collects only those objects that are created by new keyword. So if you have created any object without new, you can use finalize method to perform cleanup processing (destroying remaining objects).

gc() method

The gc() method is used to invoke the garbage collector to perform cleanup processing. The gc() is found in System and Runtime classes.
  1. public static void gc(){}  

Note: Garbage collection is performed by a daemon thread called Garbage Collector(GC). This thread calls the finalize() method before object is garbage collected.

Simple Example of garbage collection in java

  1. public class TestGarbage1{  
  2.  public void finalize(){System.out.println("object is garbage collected");}  
  3.  public static void main(String args[]){  
  4.   TestGarbage1 s1=new TestGarbage1();  
  5.   TestGarbage1 s2=new TestGarbage1();  
  6.   s1=null;  
  7.   s2=null;  
  8.   System.gc();  
  9.  }  
  10. }  
Test it Now
       object is garbage collected
       object is garbage collected

Note: Neither finalization nor garbage collection is guaranteed.