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.

java.lang paketi

Java dilinde yazmak için en temel sınıfları içerir.

En önemli sınıflar Object ve Class sınıflarıdır.
Object classı class hiyerarşisinin en tepesindedir.
Class sınıfının her bir instance'ı runtime sırasında bir classı temsil eder.

Wrapper classlar genelde primitif tiplerin bir nesne gibi referans verilmesi gerektiği durumlarda kullanılırlar. Boolean, Character, Integer, Long, Float, Double sınıfları wrapper sınıflardır.
Örneğin Double nesnesi içinde double tipinde bir field barındırır.
Wrapper sınıfların primitifler ile aralarında çeviri yapan metodları vardır.
equals ve hashCode gibi standartları desteklerler.

Void classından instance alınamaz. void tipini temsil eden bir class instance'ı barındırır.

Math classı matematik fonksiyonlar sağlar.

String, StringBuffer ve StringBuilder sınıfları String işlemleri yapar.

Sistem operasyonları yapan sınıflar : ClassLoader, Process, ProcessBuilder, Runtime, SecurityManager, ve System sınıflarıdır.

Throwable sınıfı throw keywordu kullanarak fırlatılabilen nesneleri temsil eder.
Throwable sınıfının alt sınıfları exception ve error sınıflarıdır.

Classlar




ClassDescription
Boolean
The Boolean class wraps a value of the primitive type boolean in an object.
Byte
The Byte class wraps a value of primitive type byte in an object.
Character
The Character class wraps a value of the primitive type char in an object.
Character.Subset
Instances of this class represent particular subsets of the Unicode character set.
Character.UnicodeBlock
A family of character subsets representing the character blocks in the Unicode specification.
Class<T>
Instances of the class Class represent classes and interfaces in a running Java application.
ClassLoader
A class loader is an object that is responsible for loading classes.
ClassValue<T>
Lazily associate a computed value with (potentially) every type.
Compiler
The Compiler class is provided to support Java-to-native-code compilers and related services.
Double
The Double class wraps a value of the primitive type double in an object.
Enum<E extends Enum<E>>
This is the common base class of all Java language enumeration types.
Float
The Float class wraps a value of primitive type float in an object.
InheritableThreadLocal<T>
This class extends ThreadLocal to provide inheritance of values from parent thread to child thread: when a child thread is created, the child receives initial values for all inheritable thread-local variables for which the parent has values.
Integer
The Integer class wraps a value of the primitive type int in an object.
Long
The Long class wraps a value of the primitive type long in an object.
Math
The class Math contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions.
Number
The abstract class Number is the superclass of classes BigDecimalBigIntegerByteDoubleFloatIntegerLong, and Short.
Object
Class Object is the root of the class hierarchy.
Package
Package objects contain version information about the implementation and specification of a Java package.
Process
The ProcessBuilder.start() and Runtime.exec methods create a native process and return an instance of a subclass of Process that can be used to control the process and obtain information about it.
ProcessBuilder
This class is used to create operating system processes.
ProcessBuilder.Redirect
Represents a source of subprocess input or a destination of subprocess output.
Runtime
Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running.
RuntimePermission
This class is for runtime permissions.
SecurityManager
The security manager is a class that allows applications to implement a security policy.
Short
The Short class wraps a value of primitive type short in an object.
StackTraceElement
An element in a stack trace, as returned by Throwable.getStackTrace().
StrictMath
The class StrictMath contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions.
String
The String class represents character strings.
StringBuffer
A thread-safe, mutable sequence of characters.
StringBuilder
A mutable sequence of characters.
System
The System class contains several useful class fields and methods.
Thread
thread is a thread of execution in a program.
ThreadGroup
A thread group represents a set of threads.
ThreadLocal<T>
This class provides thread-local variables.
Throwable
The Throwable class is the superclass of all errors and exceptions in the Java language.
Void
The Void class is an uninstantiable placeholder class to hold a reference to the Class object representing the Java keyword void.


Interface'ler


java.lang.Interfaces


InterfaceDescription
Appendable
An object to which char sequences and values can be appended.
AutoCloseable
A resource that must be closed when it is no longer needed.
CharSequence
CharSequence is a readable sequence of char values.
Cloneable
A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.
Comparable<T>
This interface imposes a total ordering on the objects of each class that implements it.
Iterable<T>
Implementing this interface allows an object to be the target of the "foreach" statement.
Readable
Readable is a source of characters.
Runnable
The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread.
Thread.UncaughtExceptionHandler
Interface for handlers invoked when a Thread abruptly terminates due to an uncaught exception.

...

Error'ler


java.lang.Errors


ErrorDescription
AbstractMethodError
Thrown when an application tries to call an abstract method.
AssertionError
Thrown to indicate that an assertion has failed.
BootstrapMethodError
Thrown to indicate that an invokedynamic instruction has failed to find its bootstrap method, or the bootstrap method has failed to provide a call site with a target of the correct method type.
ClassCircularityError
Thrown when the Java Virtual Machine detects a circularity in the superclass hierarchy of a class being loaded.
ClassFormatError
Thrown when the Java Virtual Machine attempts to read a class file and determines that the file is malformed or otherwise cannot be interpreted as a class file.
Error
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
ExceptionInInitializerError
Signals that an unexpected exception has occurred in a static initializer.
IllegalAccessError
Thrown if an application attempts to access or modify a field, or to call a method that it does not have access to.
IncompatibleClassChangeError
Thrown when an incompatible class change has occurred to some class definition.
InstantiationError
Thrown when an application tries to use the Java new construct to instantiate an abstract class or an interface.
InternalError
Thrown to indicate some unexpected internal error has occurred in the Java Virtual Machine.
LinkageError
Subclasses of LinkageError indicate that a class has some dependency on another class; however, the latter class has incompatibly changed after the compilation of the former class.
NoClassDefFoundError
Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the newexpression) and no definition of the class could be found.
NoSuchFieldError
Thrown if an application tries to access or modify a specified field of an object, and that object no longer has that field.
NoSuchMethodError
Thrown if an application tries to call a specified method of a class (either static or instance), and that class no longer has a definition of that method.
OutOfMemoryError
Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
StackOverflowError
Thrown when a stack overflow occurs because an application recurses too deeply.
ThreadDeath
An instance of ThreadDeath is thrown in the victim thread when the (deprecated) Thread.stop() method is invoked.
UnknownError
Thrown when an unknown but serious exception has occurred in the Java Virtual Machine.
UnsatisfiedLinkError
Thrown if the Java Virtual Machine cannot find an appropriate native-language definition of a method declared native.
UnsupportedClassVersionError
Thrown when the Java Virtual Machine attempts to read a class file and determines that the major and minor version numbers in the file are not supported.
VerifyError
Thrown when the "verifier" detects that a class file, though well formed, contains some sort of internal inconsistency or security problem.
VirtualMachineError
Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating.

Exception'lar


java.lang.Exceptions



ExceptionDescription
ArithmeticException
Thrown when an exceptional arithmetic condition has occurred.
ArrayIndexOutOfBoundsException
Thrown to indicate that an array has been accessed with an illegal index.
ArrayStoreException
Thrown to indicate that an attempt has been made to store the wrong type of object into an array of objects.
ClassCastException
Thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance.
ClassNotFoundException
Thrown when an application tries to load in a class through its string name using: The forName method in class Class.
CloneNotSupportedException
Thrown to indicate that the clone method in class Object has been called to clone an object, but that the object's class does not implement the Cloneable interface.
EnumConstantNotPresentException
Thrown when an application tries to access an enum constant by name and the enum type contains no constant with the specified name.
Exception
The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.
IllegalAccessException
An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or constructor.
IllegalArgumentException
Thrown to indicate that a method has been passed an illegal or inappropriate argument.
IllegalMonitorStateException
Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.
IllegalStateException
Signals that a method has been invoked at an illegal or inappropriate time.
IllegalThreadStateException
Thrown to indicate that a thread is not in an appropriate state for the requested operation.
IndexOutOfBoundsException
Thrown to indicate that an index of some sort (such as to an array, to a string, or to a vector) is out of range.
InstantiationException
Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated.
InterruptedException
Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted, either before or during the activity.
NegativeArraySizeException
Thrown if an application tries to create an array with negative size.
NoSuchFieldException
Signals that the class doesn't have a field of a specified name.
NoSuchMethodException
Thrown when a particular method cannot be found.
NullPointerException
Thrown when an application attempts to use null in a case where an object is required.
NumberFormatException
Thrown to indicate that the application has attempted to convert a string to one of the numeric types, but that the string does not have the appropriate format.
ReflectiveOperationException
Common superclass of exceptions thrown by reflective operations in core reflection.
RuntimeException
RuntimeException is the superclass of those exceptions that can be thrown during the normal operation of the Java Virtual Machine.
SecurityException
Thrown by the security manager to indicate a security violation.
StringIndexOutOfBoundsException
Thrown by String methods to indicate that an index is either negative or greater than the size of the string.
TypeNotPresentException
Thrown when an application tries to access a type using a string representing the type's name, but no definition for the type with the specified name can be found.
UnsupportedOperationException
Thrown to indicate that the requested operation is not supported.

Enum'lar


EnumDescription
Character.UnicodeScript
A family of character subsets representing the character scripts defined in the Unicode Standard Annex #24: Script Names.
ProcessBuilder.Redirect.Type
The type of a ProcessBuilder.Redirect.
Thread.State
A thread state.

Annotation'lar


Annotation TypeDescription
Deprecated
A program element annotated @Deprecated is one that programmers are discouraged from using, typically because it is dangerous, or because a better alternative exists.
Override
Indicates that a method declaration is intended to override a method declaration in a supertype.
SafeVarargs
A programmer assertion that the body of the annotated method or constructor does not perform potentially unsafe operations on its varargs parameter.
SuppressWarnings
Indicates that the named compiler warnings should be suppressed in the annotated element (and in all program elements contained in the annotated element).

...