21 Haziran 2022 Salı

CharBuffer Sınıfı - NIO

put - char []
Örnek
Şöyle yaparız. List of char[] nesnelerini tek bir char[] nesnesine çevirir.
List<char[]> listOfCharArrays = ...

CharBuffer fullBuffer = CharBuffer.allocate(listOfCharArrays
  .stream()
  .mapToInt(array -> array.length).sum());

listOfCharArrays.forEach(fullBuffer::put);
char[] asCharArray = fullBuffer.array();

20 Haziran 2022 Pazartesi

Virtual Threads - Java 19 İle Geliyor

Giriş
Buraya nasıl geldik. Açıklaması şöyle. Yani önce Green Threads daha sonra Fiber denendi.
In the very early versions of Java, when the multithreading API was designed, Sun Microsystems were faced with a dilemma: should we use User Mode threads or map the Java thread one to one with the OS thread. All benchmarks back then showed that User Mode threads were severely inferior, increasing memory consumption without giving much in return. However this was benchmarked 20 years ago and things were quite different back then. We didn’t have such high load requirements and the Java language was still not very mature. Now the situation is different and we had few attempts of introducing user mode threads into the language. For example Fibers.

Unfortunately due to the fact that they were implemented as a separate class it was very hard to migrate your whole codebase to it and eventually they disappeared and never got merged into the language.
Loom Nedir
Project Loom'um amacı Virtual Threads. Açıklaması şöyle
Project Loom introduces lightweight user-mode threads called Virtual Threads as instances of java.lang.Thread.
Loom tek proje değil. Açıklaması şöyle
There are several Java projects that have a very specific task to achieve. Those are for example Valhala, Panama, Amber and of course Loom. Loom’s goal is to overhaul the concurrency model of the language. They aim to bring virtual threads, structured concurrency and few other smaller things (for now).
Loom da bir çok JEP teklifinden oluşuyor. Açıklaması şöyle
Per the JDK release process, features in Project Loom were broken down into several JEPs (JDK Enhancement Proposals) and made available in different JDK releases.
Loom Tarihçesi
Açıklaması şöyle. Java 19 ile preview olarak geldi. Java 21 ile dile dahil oldu
Project Loom (2017):
The Loom project was started to provide a functionality of virtual threads in java.

Java 19 (2022):
Virtual threads were proposed as a preview feature by JEP 425

Java 21 (2023):
Virtual threads are finalised and released.

Virtual Thread Nasıl Çalışır
Açıklaması şöyle
We all know blocking a thread is evil and negatively affects your application’s performance. Well, not in this case. When a virtual thread blocks on I/O or some blocking operation in the JDK, such as BlockingQueue.take(), it automatically unmounts from the platform thread.

The JDK’s scheduler can mount and run other virtual threads on this now-free platform thread. When the blocking operation is ready to complete, it submits the virtual thread back to the scheduler, which will mount the virtual thread on an available platform thread to resume execution.

This platform thread doesn’t have to be the same from which the virtual thread was unmounted. As a result, we can now build highly concurrent applications with high throughput without consuming an increased number of threads (by default, Executors for virtual threads will use as many platform threads as the number of processors available).
Virtual Thread İle Çalışırken şunlara dikkat etmek lazım
In order to switch to Virtual threads we don’t have to learn new things, we just have to unlearn a few.
  • Never pool Virtual Threads, they are cheap and it makes no sense
  • Stop using thread locals. They will work, but if you spawn millions of threads you will have memory problems. According to Ron Pressler: “Thread locals should have never been exposed to the end user and should have stayed as an internal implementation detail”.
Bu durumda artık klasik Thread Pool'a gerek kalmıyor. Açıklaması şöyle
Thread-per-Request Model with Virtual Threads?

One should not pool virtual threads as they are not expensive resources. One can create millions of them to handle network operations. They should be spun up on-demand and killed when their task is through, and are thus suited for short lived tasks.

These properties of virtual threads give near-optimal CPU utilization and a significant increase in performance in terms of throughput and not speed. Now that we have all the supporting data, it is safe to say that a virtual thread per request model in a Java server application is safe and more efficient than pooling platform threads.
Şeklen şöyle


Yöntem 1 - Thread. startVirtualThread metodu
Thread.startVirtualThread metodu yazısına taşıdım

Yöntem 2- Thread.ofVirtual() metodu
Thread.ofVirtual metodu yazısına taşıdım

Yöntem 3 - Executors.newVirtualThreadPerTaskExecutor() metodu
Executors.newVirtualThreadPerTaskExecutor metodu yazısına taşıdım

ForkJoinPool
Virtual Threads ForkJoinPool ile çalıştırılır. Yukarıdaki çıktıdan da görülebilir. Normalde ForkJoinPool işlemci sayısı kadar thread ile başlar. Bu sayı jdk.virtualThreadScheduler.parallelism ile değiştirilebilir. Açıklaması şöyle
Moreover, you can control the initial and maximum size of the carrier thread pool using the jdk.virtualThreadScheduler.parallelism, jdk.virtualThreadScheduler.maxPoolSize and jdk.virtualThreadScheduler.minRunnable configuration options. These are directly translated to constructor arguments of the ForkJoinPool.

Unloading İşlemi - Blocking Çağrılar İçin Gerekir
Virtual thread I/O  veya bir blocking işlem yapacaksa unload edilir (örneğin BlockingQueue.take())
İşlem hazır olunca virtual thread tekrar load edilir.  Açıklaması şöyle. Yani Unload işlemi için JDK'daki bir çok kod tekrar yazılmış.
To implement virtual threads, as mentioned above, a large part of Project Loom’s contribution is retrofitting existing blocking operations so that they are virtual-thread-aware. That way, when they are invoked, they free up the carrier thread to make it possible for other virtual threads to resume.

For example, whenever code blocks on a semaphore, lock, or another Java concurrency primitive, this won’t block the underlying carrier thread but only signal to the runtime that it should capture the continuation of the current virtual thread, put it in a waiting queue and resume once the condition on which the blocking happened is resolved.
Ancak bir kaç işlemde istisna var.
1. Object.wait()
2. synchronized blok içinde
3. native metod çalıştırırken
4. UDP Sockets
Açıklaması şöyle
However, not all Java constructs have been retrofitted that way. Such operations include synchronized methods and code blocks. Using them causes the virtual thread to become pinned to the carrier thread. When a thread is pinned, blocking operations will block the underlying carrier thread-precisely as it would happen in pre-Loom times.
Eğer carrier therad'in bloke olma durumu varsa, virtual thread sayısı artırılır. En fazla kaç thread olabileceği  jdk.virtualThreadScheduler.maxPoolSize ile atanabilir
5. Files & DNS

synchronized
Örnek
Şu kod carrier thread'i bloke eder
var e = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 1000; i++) {
    e.submit(() -> { new Test().test(); });
}
e.shutdown();
e.awaitTermination(1, TimeUnit.DAYS);

class Test {
  synchronized void test() {
    sleep(4000);
  }
}
synchronized yerine Lock kullanılır. Şöyle yaparız
var e = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 1000; i++) {
    e.submit(() -> { new Test().test(); });
}
e.shutdown();
e.awaitTermination(1, TimeUnit.DAYS);

class Test {
  private ReentrantLock lock = new ReentrantLock();
  void test() {
    lock.tryLock();
    try {
      sleep(4000);
    } finally {
      lock.unlock();
    }
  }
}
sleep metodu
Örnek
Açıklaması şöyle
Similarly, Thread.sleep now only blocks the virtual thread, not the carrier thread.
sleep() metodu artık şöyle. Burada Thread'in VirtualThread olup olmadığı kontrol ediliyor.
public static void sleep(long millis) throws InterruptedException {
  if (millis < 0) {
    throw new IllegalArgumentException("timeout value is negative");
  }

  if (currentThread() instanceof VirtualThread vthread) {
    long nanos = MILLISECONDS.toNanos(millis);
    vthread.sleepNanos(nanos);
    return;
  }

  if (ThreadSleepEvent.isTurnedOn()) {
    ThreadSleepEvent event = new ThreadSleepEvent();
    try {
      event.time = MILLISECONDS.toNanos(millis);
      event.begin();
      sleep0(millis);
    } finally {
      event.commit();
    }
  } else {
    sleep0(millis);
  }
}
VirtualThread.sleepNanos() en sonunda şu metodu çağırıyor. Continuation.yield() kullanılıyor
@ChangesCurrentThread
private boolean yieldContinuation() {
  boolean notifyJvmti = notifyJvmtiEvents;
  // unmount
  if (notifyJvmti) notifyJvmtiUnmountBegin(false);
  unmount();
  try {
    return Continuation.yield(VTHREAD_SCOPE);
  } finally {
    // re-mount
    mount();
    if (notifyJvmti) notifyJvmtiMountEnd(false);
  }
}
Açıklaması şöyle. Yani Virtual Thread unload edilirse, her şeyi heap'te bir yerde saklanıyor
That is to say, Continuation.yield will transfer the stack of the current virtual thread from the stack of the platform thread to the Java heap memory, and then copy the stacks of other ready virtual threads from the Java heap to the stack of the current platform thread to continue execution. Performing blocking operations such as IO or BlockingQueue.take() will cause virtual thread switching just like sleep. The switching of virtual threads is also a relatively time-consuming operation, but compared with the context switching of platform threads, it is still much lighter.
Yani şöyle yapabiliriz. 1000 tane thread yaratıp hepsine sleep() yapsak bile tüm işlem yine de 4 saniye sürecektir.
var e = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 1000; i++) {
  e.submit(() -> { sleep(4000); });
}
e.shutdown();
e.awaitTermination(1, TimeUnit.DAYS);
Sockets
TCP socketlerini okurken Virtual Threads bloke olmuyor, ama UDP socketlerinde oluyor

Files & DNS
Açıklaması şöyle. Yani bu işlemlerde Carrier Thread sayısı otomatik artırılıyor
Yes, there are more APIs that cause thread pinning and blocking of the carrier thread, namely all file operations (such as reading from a FileInputStream, writing to a file, listing directories, etc.), as well as resolving domain names to IP addresses using InetSocketAddress.

That might look worrying, but Loom does take some remedial steps. If you take a look at the source code of FileInputStream, InetSocketAddress or DatagramSocket, you'll notice usages of the jdk.internal.misc.Blocker class. Invocations to its begin()/ end() methods surround any carrier-thread-blocking calls.
...
In other words, the carrier thread pool might be expanded when a blocking operation is encountered to compensate for the thread-pinning that occurs. A new carrier thread might be started, which will be able to run virtual threads.





Reified Generics

Giriş
Açıklaması şöyle. Type Erasure yazısına bakabilirsiniz.
Version 5 of Java brought generics. However, the language designers were keen on preserving backward compatibility: Java 5 bytecode was required to interact flawlessly with pre-Java 5 bytecode. That's why generic types are not written in the generated bytecode: it's known as type erasure. The opposite is reified generics, where generic types would be written in the bytecode.

Generic types being only a compile-time concern creates a couple of issues
Problem 1
Elimizde şöyle bir kod olsun. Bu iki kod için de üretilen bytecode aynıdır
class Bag {
  int compute(List<Foo> persons) {}
  int compute(List<Bar> persons) {}
}
Problem 2
Nesneyi aynı tipte geri alabilmek için şöyle yaparız. Yani Class<T> parametresini geçmek gerekir.
public interface BeanFactory {
  <T> T getBean(Class<T> requiredType);
}
Eğer Java'da Reified Generics olsaydı şöyle olurdu
public interface BeanFactory {
    <T> T getBean();
}



15 Haziran 2022 Çarşamba

Optional Nasıl Kullanılmalı - Optional Method Argument

Giriş
Bu konuyla ilgili aslında tam bir uzlaşma yok. Bazılarına göre bu durum kabul edilebilir.

Taraf 1'in Savunması
Optional method argument olmamalı diyenlerin açıklaması şöyle. Yani "Never use Optional as a method argument" diyenler. Bunlara Taraf 1 diyelim
Optional is not a replacement for null nor a fancy way to prevent NullPointerException. It is to indicate that the question is unanswerable, like: what is the average age of an empty list of persons.

Optionals should never be passed on, but unboxed by the calling code as soon as possible.
Taraf 1'in açıklaması şöyle. Yani null değer istemiyorsak @NonNull kullanarak zaten bunu yakalayabiliriz.
While writing a method it is recommended to annotate the required argument with @NonNull . Since author of the code knows which argument is required and which not . So better to use basic validation and throw exception in case of validation failed.
Bu konuda bir yazı da şöyle
In Java, there's no way to know whether a variable is null. To be explicit, Java 8 introduced the Optional type. From Java 8 onward, returning an Optional implies the underlying value can be null; returning another type implies it cannot.

However, the developers of Optional designed it for return values only. Nothing is available in the language syntax for methods parameters and return values. To cope with this, a bunch of libraries provide compile-time annotations:
NotNull anotasyonu sağlayan bazı kütüphaneler şöyle

Taraf 2'nin Savunması
Optional method argument  olabilir diyenlerin açıklaması şöyle. Bunlara Taraf 2 diyelim.
Passing an Optional result to another method, without any semantic analysis; leaving that to the method, is quite alright.
Optional method argument olmamalı diyenler şöyle kodlar olmamalı diyorlar.
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}
Optional Metod Argument İstemiyorsak

Çözüm 1 - Optional Parametreden Kurtulmak İçin Overload metod kullanmak

Aşağıdaki örneklerde her iki tarafın da haklı olduğu örnekler verdim.

Örnek - Tek parametre null olabiliyorsa
Eğer Optional kullansaydık şöyle yapardık
public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}
Taraf 1'e uyarak Optional parametreden kurtulmak için metodumuzu overload ederek şöyle yapalım. Burada Attachment parametresi null olabiliyor.
public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}
Overload edilen metodun birinci halini çağırmak için şöyle yaparız
SystemMessage withoutAttachment = new SystemMessage("title", "content");
Overload edilen metodun ikinci halini çağırmak için şöyle yaparız
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);

Örnek - İki parametre null olabiliyorsa
Bu örnekte aslında Optional parametre geçmemenin kodu daha da zorlaştırdığı görülüyor.
Optional kullansaydık şöyle yapardık.
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}
Taraf 1'e uyarak Optional parametreden kurtulmak için metodumuzu overload ederek  şöyle yapalım. Burada null olabilecek parametreler için metod overload ediliyor.
public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}
Bu durumda kodu çağırmak için şöyle yaparız. Görüldüğü gibi kod daha karmaşık hale gelmeye başladı
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}
Çözüm 2 - Optional Parametreden Kurtulmak İçin Metod Çağrısında Optional.orElse Kullanmak
Örnek
Metodumuzdaki Optional parametreleri kaldırarak şöyle yaparız. Metoda null parametre gelecekmiş gibi kabul edip, kontrol etmek gerekir.
public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}
Bu durumda kullanmak için şöyle yaparız.
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));
Örnek
Elimizde şöyle bir kod olsun
public void doAction() {
  OptionalInt age = getAge();
  Optional<Role> role = getRole();
  applySettings(name, age, role);
}
Metod imzasında Optional parametre kullanmamaya karar verdiysek şöyle yaparız. Burada default değerler kullanılıyor.
public void doAction() {
OptionalInt age = getAge(); Optional<Role> role = getRole(); applySettings(name, age.orElse(defaultAge), role.orElse(defaultRole)); }