Bu yazı da sizlerle java ile uygulama geliştirmede ayrı bir yere sahip olan ThreadLocal ve InheritableThreadLocal sınıflarının kullanımı konusunda tecrübelerimi paylaşmak istiyorum.
ThreadLocal sınıfı default constructor olarak adlandırılan parametre almamış bir constructor’a sahiptir.
ThreadLocal sınıfının sahip olduğu metodlar aşağıda listelenmiştir.
Eğer Thread safe olamayan bir nesne thread’ler arasında paylaşılıyorsa bu taktirde bu nesneye erişimin synchronize kavramı kullanılarak kontrol altına alınması gerekmektedir. Bu şekilde farklı thread’ler nesneye erişmek istediğinde, aynı anda sadece bir thread’in eriştiğinden emin olunur.
ThreadLocal, thread safe olmayan nesnelerin, sadece thread tarafından erişilebilir bir şekilde tanımlanmasını sağlayarak, bir nevi thread safe sağlamış olur. Buna örnek olarak thread safe olmadığını bildiğimiz SimpleDateFormat sınıfını gösterebiliriz. Normalde SimpleDateFormat sınıfı içerisinde formatlama aşamasında kullanılmak üzere global değişkenler mevcuttur ve bunlar sınıfın state’ini değiştirmektedir. SimpleDateFormat nesnesi formatlama sırasında bazı ara sonuçları global değişkenlere yerleştirmektedir. Eğer çoklu thread olan bir sistemde aynı nesneyi kullanmaya çalışırsak, SimpleDateFormat nesnesi içerisindeki ara sonuçlar birbirine girecek ve istenilen sonuç elde edilemeyebilecektir. Bu durumda thread’ler açısından güvenli olmayan bir durum oluşmuş olur.
Ayrıca ThreadLocal sınıfını, aynı thread içerisinde belli bir bilgiyi, sınıflar veya metodlar arasında taşımak için kullanabiliriz. Bu yönetemi bir çok framework kullanmakta, böylece context, request, session gibi bilgilere uygulamanın her bir alanından erişimi sağlanabilmektedir. Fakat bu yönteminde zaman zaman sıkıntılara neden olabilecek durumları da karşımıza çıkmaktadır. Bir sonraki yazılarda bunlara değinmeye çalışacağım.
ThreadLocal implementasyonunda WeakReference kullanılır. Garbage Collector çalıştığında WeakReference kullanan nesneler, toplanmaya daha müsatittir. Bundan dolayı ThreadLocal için güçlü bir tanımlama yapılmadığı durumlarda, nesneye erişilmeye çalışıldığında null değeriyle karşılaşılabilir. Güçlü bir referans tanımlaması yapmak için de değişkenin final ve static olarak tanımlanması tavsiye edilmektedir.
SimpleDateFormat’ın kullanımına bir örnek yapalım.
ThreadLocal’i bir değişken olarak barındıran ve bir başlangıç olarak bir SimpleDateFormat nesnesi set eden sınıf oluşturuyoruz. Dikkat ederseniz Garbage Collector tarafından hemen toplanmasını önlemek amacıyla final static olarak tanımladık ve bu değişkene erişimi kontrollü bir şekilde vermek amacıyla da getSimpleDateFormat() ve setSimpleDateFormat() metodlarını ekledik.
Uygulama sunucularının requestleri işleme tarzını şu şekilde açıklayabiliriz: Uygulama sunucusu tarafında requrestleri işlemek üzere Thread pool’lar tutulmaktadır. Thread pool içerisindeki thread sayısı, uygulama sunucusu üzerindeki ayarlanabilir bir değişkenle konfigüre edilebilir. Böylece aynı anda daha fazla requeste cevap verebilmenin altyapısı hazırlanmış olur (gerekli kaynakların da sağlanması şartıyla). Uygulama sunucusuna bir request geldiğinde, bu requeste karşılık bir response döndürme anına kadar, herşey (Filter ve Servletler de dahil) sunucunun sağladığı bir thread pool’a ait bir thread üzerinde gerçekleşmektedir. Sunucuya bir request geldiğinde hemen bir thread tahsis edilerek, response dönülünceye kadar her iş bu thread içerisinde halledilir. Response sonunda da ilgili thread tekrar thread pool’a iade edilir. Böylece yeni bir request için ilgili thread kullanılabilir durumda olacaktır.
Bu işleyiş göz önüne alındığında Thread’in requesti işlemeye başladığı anlarda veya işlem aşamasında bir ThreadLocal değişkenine herhangi bir değer set edildiğinde, response dönene kadar uygulamanın her yerinden (yeni bir thread yapılarak bir dallanma olmadığı sürece) bu değişkene thread safe olarak ulaşmak mümkün olacaktır. Fakat işleyişten anlaşıldığı üzere thread’ler sonlandırılmadan yeni bir requesti işlemek üzere beklemektedir. Yani aynı thread birden fazla request için kullanılmaktadır (Farklı session ve clientlara ait de olsalar). Bu durumda sadece request’e ait bir bilgiyi thread boyunca taşımak istiyorsak, request sonunda ThreadLocal değişkenindeki değeri temizlemek bir çözüm olacaktır. Bu yöntem aynı zamanda ThreadLocal’e set edilen ve daha sonra gerek duyulmacak nesneleri garbage collector’ün toplamasına da açık hale getirecektir. Böylce bellekte bir kaçak (leak) önlenmiş olacaktır.
Bir örnek üzerinde bunun uyuglamasını aşağıdaki şekilde görebiliriz: İlk önce UserStorage adında içerisinde ThreadLocal tanımlı User nesnesini tutabilen bir sınıf oluşturduk.
/UserTestServlet servlet’ine bir istek yapılmak istenildiğinde gelen her request’in filter üzerinden akması sağlanır. UserStorage sınıfı içerisindeki ThreadLocal sınıfı kullanılarak tanımlanmış değişkene bir user bilgisi set edilir. Bu set işleminden sonra request’in devam etmesi sağlanır ve UserTestServlet’inin doGet metoduna ulaşıldığında halen aynı thread üzerinde işlemler döndüğünden dolayı UserStorage.getUser().getName() denildiğinde aslında az önce filter içerisinde set edilen user bilgilerine erişilmiş olacaktır. Belli başlı tanınmış frameworklar (Spring,ZK vb.) aslında context, request gibi bilgilere uygulamanın heryerinden erişilebilmeyi buna benzer bir yöntemle çözerler. Ayrıca transaction işlemleri de ThreadLocal kullanılarak çözüme kavuşturulabilir. Örnek vermek gerekirse: Spring framework’üne ait RequestContextHolder sınıfı incelendiğinde ThreadLocal sınıfının benzer amaçla kullanıldığı görülebilir. Buradaki NamedThreadLocal sınıfı, sadece name değişkeni eklemiş, ThreadLocal’i extends etmiş Spring’e ait basit bir alt sınıftır.
Faydalı olması dileğiyle.
http://tutorials.jenkov.com/java-concurrency/threadlocal.html
http://javarevisited.blogspot.com.tr/2012/05/how-to-use-threadlocal-in-java-benefits.html
http://stackoverflow.com/questions/940506/threadlocal-resource-leak-and-weakreference
http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage
ThreadLocal
ThreadLocal sınıfını kullanarak, çalışma anında bir thread tarafından set edilen değişkenlerin, sadece yine ilgili thread tarafından okunabilirliği sağlanabilir. Bir uygulama içerisinde birden fazla thread çalışıyor olabilir. Bu durumda her bir thread’e özgü değişkenler ve değerleri set edilebilir ve istenildiği durumda tekrar geri çağrılabilir.ThreadLocal sınıfı default constructor olarak adlandırılan parametre almamış bir constructor’a sahiptir.
ThreadLocal sınıfının sahip olduğu metodlar aşağıda listelenmiştir.
- T initialValue() : Mevcut thread içerisinde ilk set edilen başlangıç değerini döndürür.
- T get() : Mevcut thread içerisinde saklanmış olan değişkene ait değeri döndürür. Eğer herhangi bir değer set edilememişse initialValue ile geçilen değer döndürülür.
- void set(T value) : Mevcut thread içerisinde ilgili değişkene değer set eder.
- void remove() : Mevcut thread içerinde saklanmış olan ilgili değişkene ait veri temizlenir.
- ThreadLocal<S> withInitial(Suplier suplier) : İlke değere sahip bir ThreadLocal değişkeni oluşturmak için kullanılır. Java 8 ile birlikte gelmiştir.
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal’ı nerede kullanmalıyız ve nasıl faydanabiliriz bunlara bir göz atalım.Eğer Thread safe olamayan bir nesne thread’ler arasında paylaşılıyorsa bu taktirde bu nesneye erişimin synchronize kavramı kullanılarak kontrol altına alınması gerekmektedir. Bu şekilde farklı thread’ler nesneye erişmek istediğinde, aynı anda sadece bir thread’in eriştiğinden emin olunur.
ThreadLocal, thread safe olmayan nesnelerin, sadece thread tarafından erişilebilir bir şekilde tanımlanmasını sağlayarak, bir nevi thread safe sağlamış olur. Buna örnek olarak thread safe olmadığını bildiğimiz SimpleDateFormat sınıfını gösterebiliriz. Normalde SimpleDateFormat sınıfı içerisinde formatlama aşamasında kullanılmak üzere global değişkenler mevcuttur ve bunlar sınıfın state’ini değiştirmektedir. SimpleDateFormat nesnesi formatlama sırasında bazı ara sonuçları global değişkenlere yerleştirmektedir. Eğer çoklu thread olan bir sistemde aynı nesneyi kullanmaya çalışırsak, SimpleDateFormat nesnesi içerisindeki ara sonuçlar birbirine girecek ve istenilen sonuç elde edilemeyebilecektir. Bu durumda thread’ler açısından güvenli olmayan bir durum oluşmuş olur.
Ayrıca ThreadLocal sınıfını, aynı thread içerisinde belli bir bilgiyi, sınıflar veya metodlar arasında taşımak için kullanabiliriz. Bu yönetemi bir çok framework kullanmakta, böylece context, request, session gibi bilgilere uygulamanın her bir alanından erişimi sağlanabilmektedir. Fakat bu yönteminde zaman zaman sıkıntılara neden olabilecek durumları da karşımıza çıkmaktadır. Bir sonraki yazılarda bunlara değinmeye çalışacağım.
ThreadLocal implementasyonunda WeakReference kullanılır. Garbage Collector çalıştığında WeakReference kullanan nesneler, toplanmaya daha müsatittir. Bundan dolayı ThreadLocal için güçlü bir tanımlama yapılmadığı durumlarda, nesneye erişilmeye çalışıldığında null değeriyle karşılaşılabilir. Güçlü bir referans tanımlaması yapmak için de değişkenin final ve static olarak tanımlanması tavsiye edilmektedir.
SimpleDateFormat’ın kullanımına bir örnek yapalım.
ThreadLocal’i bir değişken olarak barındıran ve bir başlangıç olarak bir SimpleDateFormat nesnesi set eden sınıf oluşturuyoruz. Dikkat ederseniz Garbage Collector tarafından hemen toplanmasını önlemek amacıyla final static olarak tanımladık ve bu değişkene erişimi kontrollü bir şekilde vermek amacıyla da getSimpleDateFormat() ve setSimpleDateFormat() metodlarını ekledik.
public class ThreadSimpleDateFormat {
private static final ThreadLocal SIMPLE_DATE_FORMAT = new ThreadLocal() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("dd/MM/yyyy", new Locale("tr", "TR"));
};
};
public static SimpleDateFormat getSimpleDateFormat() {
return SIMPLE_DATE_FORMAT.get();
}
public static void setSimpleDateFormat(SimpleDateFormat simpleDateFormat) {
SIMPLE_DATE_FORMAT.set(simpleDateFormat);
}
}
İkinci olarak runnable’ı implemente eden bir ThreadTask sınıfı oluşturduk. Böylece thread özelliği kazandırdımız sınıfın task metodunda ThreadSimpleDateFormat sınıfının getSimpleDateFormat metodunu çağırarak ilgili thread’e has olan bir SimpleDateFormat nesnesini elde etmiş olduk.
public class ThreadTask implements Runnable {
public void task() {
int interval = 0;
for (int i = 0; i < 5; i++) {
SimpleDateFormat simpleDateFormat = ThreadSimpleDateFormat
.getSimpleDateFormat();
String formatedDate = simpleDateFormat.format((System
.currentTimeMillis() + interval));
interval = interval + 1000 * 60 * 60 * 24;
System.out.println("Thread : " + Thread.currentThread().getName()
+ " - Formated Date: " + formatedDate);
}
}
@Override
public void run() {
new ThreadTask().task();
}
}
Test için basit bir sınıf.
public class SimpleDateFormatTest {
public static void main(String[] args) {
Thread th1 = new Thread(new ThreadTask());
Thread th2 = new Thread(new ThreadTask());
Thread th3 = new Thread(new ThreadTask());
th1.start();
th2.start();
th3.start();
}
}
Şimdi de ThreadLocal sınıfının bir web uygulamasında hangi amaçla kullanılabileceğine bir örnek verelim.
Uygulama sunucularının requestleri işleme tarzını şu şekilde açıklayabiliriz: Uygulama sunucusu tarafında requrestleri işlemek üzere Thread pool’lar tutulmaktadır. Thread pool içerisindeki thread sayısı, uygulama sunucusu üzerindeki ayarlanabilir bir değişkenle konfigüre edilebilir. Böylece aynı anda daha fazla requeste cevap verebilmenin altyapısı hazırlanmış olur (gerekli kaynakların da sağlanması şartıyla). Uygulama sunucusuna bir request geldiğinde, bu requeste karşılık bir response döndürme anına kadar, herşey (Filter ve Servletler de dahil) sunucunun sağladığı bir thread pool’a ait bir thread üzerinde gerçekleşmektedir. Sunucuya bir request geldiğinde hemen bir thread tahsis edilerek, response dönülünceye kadar her iş bu thread içerisinde halledilir. Response sonunda da ilgili thread tekrar thread pool’a iade edilir. Böylece yeni bir request için ilgili thread kullanılabilir durumda olacaktır.
Bu işleyiş göz önüne alındığında Thread’in requesti işlemeye başladığı anlarda veya işlem aşamasında bir ThreadLocal değişkenine herhangi bir değer set edildiğinde, response dönene kadar uygulamanın her yerinden (yeni bir thread yapılarak bir dallanma olmadığı sürece) bu değişkene thread safe olarak ulaşmak mümkün olacaktır. Fakat işleyişten anlaşıldığı üzere thread’ler sonlandırılmadan yeni bir requesti işlemek üzere beklemektedir. Yani aynı thread birden fazla request için kullanılmaktadır (Farklı session ve clientlara ait de olsalar). Bu durumda sadece request’e ait bir bilgiyi thread boyunca taşımak istiyorsak, request sonunda ThreadLocal değişkenindeki değeri temizlemek bir çözüm olacaktır. Bu yöntem aynı zamanda ThreadLocal’e set edilen ve daha sonra gerek duyulmacak nesneleri garbage collector’ün toplamasına da açık hale getirecektir. Böylce bellekte bir kaçak (leak) önlenmiş olacaktır.
Bir örnek üzerinde bunun uyuglamasını aşağıdaki şekilde görebiliriz: İlk önce UserStorage adında içerisinde ThreadLocal tanımlı User nesnesini tutabilen bir sınıf oluşturduk.
public class UserStorage {
private static final ThreadLocal USER = new ThreadLocal();
public static void setUser(User user) {
UserStorage.USER.set(user);
}
public static User getUser() {
return UserStorage.USER.get();
}
public static void removeUser() {
UserStorage.USER.remove();
}
}
Sonra da bir UserFilter adında bir filter oluşturup tüm trafik bu filter üzerinden akacak şekilde bir tanımalama yaptık. Her bir request bu filter’a uğrayacak ve bir user bilgisini UserStorage sınıfı üzerinden ThreadLocal değişkenine set edecektir. Bu işlem sırasında kullanıcıların rahat gözlemlenmesi için count değişkeni her defasında bir artırılır. Daha sonra chain.doFilter metodu kullanılarak requestin orjinal mecrasında akması sağlanır. Dikkat edilirse try finally bloğu kullanılarak filter’ın sonlanması anında (requestin sonlanması manasına gelir) ThreadLocal içerisinde değer temizlenmektedir.
@WebFilter("/*")
public class UserFilter implements Filter {
int count;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
System.out.println("Filter Thread name : "
+ Thread.currentThread().getName());
User user = UserStorage.getUser();
if (user != null) {
System.out.println("Name before: " + user.getName());
} else {
if (user != null) {
System.out.println("Name before: null" );
}
}
List roles = new ArrayList();
roles.add("admin");
roles.add("deployer");
UserStorage.setUser(new User("Kullanici-" + ++count, roles));
System.out
.println("Name after: " + UserStorage.getUser().getName());
chain.doFilter(request, response);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
} finally {
UserStorage.removeUser();
}
}
}
Daha sonra da işleyişi test etmek için bir Servlet oluşturuyoruz.
@WebServlet("/UserTestServlet")
public class UserTestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
System.out.println("Name in doGet: " + UserStorage.getUser().getName());
}
}
Aslında yukarıda kodda yapılan işlem şudur:
/UserTestServlet servlet’ine bir istek yapılmak istenildiğinde gelen her request’in filter üzerinden akması sağlanır. UserStorage sınıfı içerisindeki ThreadLocal sınıfı kullanılarak tanımlanmış değişkene bir user bilgisi set edilir. Bu set işleminden sonra request’in devam etmesi sağlanır ve UserTestServlet’inin doGet metoduna ulaşıldığında halen aynı thread üzerinde işlemler döndüğünden dolayı UserStorage.getUser().getName() denildiğinde aslında az önce filter içerisinde set edilen user bilgilerine erişilmiş olacaktır. Belli başlı tanınmış frameworklar (Spring,ZK vb.) aslında context, request gibi bilgilere uygulamanın heryerinden erişilebilmeyi buna benzer bir yöntemle çözerler. Ayrıca transaction işlemleri de ThreadLocal kullanılarak çözüme kavuşturulabilir. Örnek vermek gerekirse: Spring framework’üne ait RequestContextHolder sınıfı incelendiğinde ThreadLocal sınıfının benzer amaçla kullanıldığı görülebilir. Buradaki NamedThreadLocal sınıfı, sadece name değişkeni eklemiş, ThreadLocal’i extends etmiş Spring’e ait basit bir alt sınıftır.
private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal("Request attributes");
InhertibaleThreadLocal
InheritableThreadLocal sınıfı ThreadLocal sınıfından türetilerek elde edilmiş bir sınıftır. Normalde ThreadLocal sınıfı kullanılarak tanımlanmış bir değişken sadece ilgili thread içerisinden ulaşılabirlirdir ve child thread ler dahil hiç bir thread bu değişkene ulaşamaz. Fakat InheritableThreadLocal sınıfı kullanılarak tanımlanan bir değişkenin değeri, child thread oluşrulurken childValue metodu aracılığıyla bu thread’e de aktarılır. Fakat daha sonrasında bu değerin kontrolu child thread’e aittir. Parent thread içerisindeki değişkenin değerinin değişmesi child threadi etkilemez. Bundan dolayı child thread’i kullanırken dikkatli olunması gerekmektedir. Belki bir sonraki paylaşımda bunu örneklerle açıklama imkanımız olur.
Faydalı olması dileğiyle.
Kaynaklar:
https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.htmlhttp://tutorials.jenkov.com/java-concurrency/threadlocal.html
http://javarevisited.blogspot.com.tr/2012/05/how-to-use-threadlocal-in-java-benefits.html
http://stackoverflow.com/questions/940506/threadlocal-resource-leak-and-weakreference
http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage
Hiç yorum yok :
Yorum Gönder