Java EE 7 – Concurrency Utilities
Concurrency Utilities (JSR 166) standardı, Java EE 7 ana belirtim altında getirilen yeni bir Java EE standardıdır. Bu standart Java 1.5 ile sunulan Executor API’nin konteyner taraflı yönetimli nesneler olarak sunulmasını konu almaktadır.
Executor API, Oracle tarafından sunulan Java SE dökümanlarında High Level Concurrency Objects başlığı altında yer almaktadır. Executor API sunduğu nesneler aracılığıyla Thread yönetimini verimli olarak sağlamaktadır. Bu verimlilik genel olarak çeşitli Thread Pool mekanizmalarıyla sağlanmaktadır.
Thread Pooling
Thread havuzları, belirlenen bir sayıda Worker Thread (İşçi Thread) oluşturumu ve kullanıcılar tarafından tanımlanan görev nesnelerinin bu işçi thread’ler ile koşturulmasını ihtiva etmektedir. Bu sayede azami olarak Pool Size miktarında görev koşturulmakta ve sistem kaynakları verimli kullanılmaktadır.
Yukarıdaki resim Thread Havuzlarını daha iyi anlamada yardımcı olabilir. Örneğin sol tarafta bir for döngüsüyle 5000 adet görev nesnesi uygulamanız üzerinde koşturulmak isteniyor olsun, bu 5000 adet görev nesnesinin Thread Havuzu kullanmadan devreye sokmaya çalışmak, sistem kaynaklarını tehdit edecektir. Bu tehdit meselesini sisteminizde çalışan herhangi bir uygulamanın devreye soktuğu Thread sayısına bakarak daha iyi anlayabiliriz.
Örneğin bu benim makinamda koşturulan bir dizi process ve yönettiği Thread sayısını göstermektedir. idea.exe uygulamasının 71 adet iş parçacığına sahip olduğu açıkça görülebilir. Diğer uygulamalara bakıldığında, daha az thread kullandığı görülüyor. Peki benim 5000 adet görevim varsa ve bu 5000 görevleri 5000 ayrı iş parçacığı olarak sisteme sunarsam, iyi bir mimari kurmuş olur muyum? (Tabi ki hayır : )
Oysa belirlenen sayılarda Worker thread’ler ile bu 5000 görevden seçimler (rastgele veya öncelikli) yaparak koşturursam, sistem kaynaklarını verimli kullanmış olurum ve sistemi gereksiz strese sokmam. Yukarıdaki grafikte 5 adet Worker thread tanımlandığı için, bu görevlerin koşacağı bir sisteme etkisi azami 5 thread olarak görülecektir. Bir worker thread işini bitirdiğinde hazırda bekleyen görevlerden biri seçilerek görevler bitene kadar koşturum devam edecektir. İşte Java SE 1.5 ile gelen Executor API’nin temel felsefesi, çeşitli özelliklerde Thread havuz ortamı sunarak verimli Thread yönetimini sağlamaktır. Worker thread (işçiler) ise, Thread üreticileri tarafından üretilen bilindik Thread türünden nesnelerdir.
Executor API içerisindeki en temel arayüz tipleri ExecutorService ve ScheduledExecutorService’dir. Bu arayüz tiplerine karşılık gelecek nesneler ise Executors sınıfının çeşitli static metodlarıyla oluşturulan Thread Pool nesneleri olmaktadır.
Java EE 7 ile birlikte gelen yeni Concurrency Utilities standardı ise, belirtilen bu nesne tiplerini konteyner servisleri tarafından yönetilebilir ve enjekte edilebilir kılmaktadır. Fakat konteyner servislerinin sunduğu bu nesne tipleri, yönetilebilirliği temsil etmesi açısından ManagedExecutorService ve ManagedScheduledExecutorService isimlendirmeleriyle karşımıza çıkmaktadır. Bu arayüzlerin UML diagramına bakıldığında, Java SE Executor API arayüzlerinden genişlediği görülebilir.
Managed ile başlayan arayüzler temel olarak Java 1.5 ile gelen Executor API bileşenlerinin sunduğu operasyonlarla aynı operasyonları sunmaktadır. Buradaki fark yeni nesnelerin birer konteyner kaynağı olarak geliştiricilere sunulmasıdır.
Konteyner kaynakları, uygulama sunucuları tarafından yönetilen özel nesnelerdir. Örnek olarak DataSource’lar, JMS kaynakları ve yeni Concurrency Utilities standardıyla Concurrency birimleri konteyner kaynaklarına örnek verilebilir.
Konteyner kaynakları, uygulama sunucuları üzerinde barınan ve özel fonksiyonları icra eden nesnelerdir. Bu nesnelere JNDI (Java Naming and Directory Interfaces) standardıyla erişim kurulabilir. Bu erişim operasyonu temel olarak @Resource notasyonu veya Context arayüzü türünden nesneler (Örn: InitialContext) üzerinden sağlanabilmektedir.
Concurreny kaynakları oluşturmak
Concurreny Utilities dahilindeki yönetimli executor nesneleri, Java EE 7 belirtimini destekleyen uygulama sunucularında oluşturulabilmektedir. Bu oluşturma süreci Glassfish 4 için asadmin komut seti aracılığıyla veya Glassfish Admin Console üzerinden görsel olarak tanımlanabilmektedir.
Komut dizini işlemleri için Glassfish uygulama sunucusunda /bin dizininde yer alan asadmin aracı kullanılmaktadır. Örneğin aşağıda asadmin aracı çalıştırılmış ve create-managed-executor-service veya create-managed-scheduled-executor-service komutlarıyla etkileşimli olarak yönetimli executor nesneleri oluşturulmuştur. Konteyner kaynakları uygulama ortamlarına JNDI standardında tanımlanan erişim ifadeleriyle sağlanabildiği için, o konteyner kaynağını temsil eden eşsiz erişim ifadesi konsol ekranında girilmelidir.
Oluşturulan bu yönetimli executor nesneleri Glassfish Admin Console üzerinden görülebilir haldedir. Benzer şekilde bu ekleme ve düzenleme işlemleri admin console üzerinden de yapılabilmektedir.
Glassfish 4 üzerinde konteyner kaynakları Resources sekmesi altında yer almaktadır. Concurrency kaynakları ise bir alt sekme olan Concurrent Resources altında yer almaktadır. Konteyner tarfından yönetilebilir olarak oluşturduğumuz ManagedExecutorService ve ScheduledManagedExecutorService kaynakları bu kısımda bulunurken, uygulama sunucusu üzerinde hazır olarak tanımlanmış __default ön ekli Concurreny kaynakları da ayrıca bulunmaktadır. İstendiği takdirde halihazırda bulunan nesneler kullanılabilmektedir.
Concurrency Utilities Kaynaklarının Elde Edilmesi
Uygulama sunucularında barınan Concurrency kaynakları @Resource notasyonu üzerinden enjeksiyon yöntemiyle veya InitialContext nesneleri üzerinden elde edilebilmektedir. Bu notasyon ve nesne türleri, sadece Concurrency kaynakları için değil aynı zamanda diğer tüm konteyner kaynaklarına standart erişim sağlamaktadır.
@WebServlet(urlPatterns = "/kodcu",name = "KodcuServlet")
public class KodcuServlet extends HttpServlet {
@Resource // (1)
private ManagedExecutorService defaultmanagedExecutorService;
@Resource // (2)
private ManagedScheduledExecutorService defaultScheduledExecutorService;
@Resource(lookup = "concurrent/KodcuExecutor") // (3)
private ManagedExecutorService managedExecutorService;
@Resource(lookup = "concurrent/KodcuScheduledExecutor") // (4)
private ManagedScheduledExecutorService scheduledExecutorService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
InitialContext context=new InitialContext(); // (5)
ManagedExecutorService managedExecutorServiceWithContext = (ManagedExecutorService) context.lookup("concurrent/KodcuExecutor");
...
}
}
Örneğin yukarıdaki Servlet sınıfında olduğu gibi varsayılan Concurrency kaynakları (1 ve 2 numarada) ve komut dizininde JNDI name belirtilerek oluşturulan kaynaklar (3 ve 4 ) @Resource notasyonu üzerinden enjeksiyon yöntemiyle edinilmektedir. @Resource notasyonunun lookup alanı JNDI standartıyla tanımlanan eşsiz kaynak erişim ifadesiyle ilgili nesneyi elde etmektedir. @Resource notasyonu çıplak olarak kullanıldığında ise, __default önekli JNDI kaynakları enjekte edilmektedir. 5 numaralı kısımda ise, enjeksiyon yönteminden hariç olarak InitialContext nesnesi üzerinden ilgili JNDI adına göre Concurrency kaynağı elde edilmektedir.
Örnek uygulamaya https://github.com/rahmanusta/ConcurrencyUtilities bağlantısından erişebilirsiniz.
Tekrar görüşmek dileğiyle.
Tag:backend, concurrency, injection, javaee, javaee7