[java] JDK21 is coming

foreword

But how long, JDK 21 will come out soon, it seems that Java has really grown in recent years.
At present, the latest stable version of Java is JDK 20, but this is a transitional version. JDK21 is the LTS version, and it will be released soon. It will be officially released in September this year (that is, September 2023).
But, guess what, you must still be using Java 8!

A smoother concurrent programming model

If you still feel that there is no need to toss about the previous JDK17, then JDK21 really needs to pay attention. Because JDK21 introduces a new type of concurrent programming model.

The current multi-threaded concurrent programming in Java is definitely another part of our headaches. It feels like it is difficult to learn and difficult to use. But looking back at friends who use other languages, there is no such trouble at all, such as GoLang, I feel that people use it very silky.

JDK21 has made great improvements in this area, making Java concurrent programming a little easier and smoother. To be precise, there are these improvements in JDK19 or JDK20.

What exactly is it? Let's take a closer look. The following is the Feature of JDK21.
insert image description here

Among them, Virtual Threads, Scoped Values, and Structured Concurrency are several functions for multi-threaded concurrent programming. We will also mainly talk about them today.

Virtual Threads

Virtual threads are coroutine-based threads that have similarities to coroutines in other languages, but also have some differences.
The virtual thread is attached to the main thread. If the main thread is destroyed, the virtual thread will no longer exist.

  • Similarities:

    • Both virtual threads and coroutines are lightweight threads, and their creation and destruction overheads are smaller than traditional operating system threads.
    • Both virtual threads and coroutines can switch between threads by suspending and resuming, thus avoiding the overhead of thread context switching.
    • Both virtual threads and coroutines can process tasks in an asynchronous and non-blocking manner, improving application performance and responsiveness.
  • the difference:

    • Virtual threads are implemented at the JVM level, while coroutines are implemented at the language level. Therefore, the implementation of virtual threads can be used with any language that supports the JVM, while the implementation of coroutines requires specific programming language support.
    • Virtual threads are a thread-based implementation of coroutines, so they can use thread-related APIs such as ThreadLocal, Lock, and Semaphore. Coroutines do not depend on threads, and usually require the use of specific asynchronous programming frameworks and APIs.
    • The scheduling of virtual threads is managed by the JVM, while the scheduling of coroutines is managed by the programming language or asynchronous programming framework. Therefore, virtual threads can better cooperate with other threads, while coroutines are better for handling asynchronous tasks.

In general, virtual threads are a new thread type that can improve the performance and resource utilization of applications, while also using traditional thread-related APIs. Virtual threads have many similarities to coroutines, but there are also some differences.

Virtual threads can indeed make multithreaded programming easier and more efficient. Compared with traditional operating system threads, the overhead of creating and destroying virtual threads is smaller, and the overhead of thread context switching is also smaller, so resource consumption and performance bottlenecks in multi-threaded programming can be greatly reduced.

Using virtual threads, developers can write code like writing traditional thread code without worrying about the number and scheduling of threads, because the JVM will automatically manage the number and scheduling of virtual threads. In addition, virtual threads also support traditional thread-related APIs, such as ThreadLocal, Lock, and Semaphore, which makes it easier for developers to migrate traditional thread code to virtual threads.

The introduction of virtual threads makes multi-thread programming more efficient, simpler and safer, allowing developers to focus more on business logic without paying too much attention to underlying thread management.

Structured Concurrency

Structured Concurrency is a programming paradigm that aims to simplify concurrent programming by providing a structured and easy-to-follow approach. Using structured concurrency, developers can create concurrent code that is easier to understand and debug, and less prone to race conditions and other concurrency-related bugs. In structured concurrency, all concurrent code is structured into well-defined units of work called tasks. Tasks are created, executed, and completed in a structured manner, and the execution of a task is always guaranteed to complete before its parent task completes.

Structured Concurrency (structured concurrency) can make multi-threaded programming easier and more reliable. In traditional multi-threaded programming, the startup, execution, and termination of threads are manually managed by developers, so problems such as thread leaks, deadlocks, and improper exception handling are prone to occur.

Using structured concurrency, developers can organize concurrent tasks more naturally, making the dependencies between tasks clearer and the code logic more concise. Structured concurrency also provides some exception handling mechanisms to better manage exceptions in concurrent tasks and avoid program crashes or data inconsistencies caused by exceptions.

In addition, structured concurrency can also prevent resource competition and starvation by limiting the number and priority of concurrent tasks. These features make it easier for developers to implement efficient and reliable concurrent programs without paying too much attention to underlying thread management.

Scoped Values¶

Scoped values ​​are a feature in JDK 20 that allow developers to create scoped values ​​that are restricted to a specific thread or task. Scoped values ​​are similar to thread-local variables, but are designed to work with virtual threads and structured concurrency. They allow developers to pass values ​​between tasks and virtual threads in a structured manner, without complex synchronization or locking mechanisms. Scope values ​​can be used to pass contextual information between different parts of the application, such as user authentication or requesting specific data.

try it out

Before proceeding with the following exploration, you need to download at least JDK19 or directly download JDK20. JDK 20 is currently (as of September 2023) the highest version officially released. If you use JDK 19, you cannot experience the Scoped Values ​​function.
insert image description here

Or simply download the Early-Access Builds of JDK 21. Download "jdk.java.net/21/" at this address, download the corresponding version...

If you are using IDEA, then your IDEA version must be at least 2022.3 or later, otherwise such a new JDK version will not be supported.
insert image description here
If you are using JDK19 or JDK20, you should set the language level to 19 or 20 in your project settings. Otherwise, you will be prompted that you cannot use the preview version function when compiling. Virtual thread is the function of the preview version. .
insert image description here

If you are using JDK21, set the language level to X -Experimental Features. In addition, because JDK21 is not an official version, you need to go to the IDEA settings (note that it is the IDEA settings, not the project settings), set this The Target bytecode version of the project is manually changed to 21. Currently, the highest option is 20, which is JDK20. After setting to 21, you can use these functions in JDK21.
insert image description here

Examples of virtual threads

How do we start the thread now?
First declare a thread class, implements from Runnable, and implement the run method.

public class SimpleThread implements Runnable{
    
    

    @Override
    public void run() {
    
    
        System.out.println("当前线程名称:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
    }
}

Then you can use this thread class and start the thread.

Thread thread = new Thread(new SimpleThread());
thread.start();

Quite satisfactory, no problem.
After having a virtual thread, how to achieve it?

Thread.ofPlatform().name("thread-test").start(new SimpleThread());

Below are several ways to use virtual threads.

  1. Start a virtual thread directly
Thread thread = Thread.startVirtualThread(new SimpleThread());
  1. Use ofVirtual(), builder mode to start virtual threads, you can set thread name, priority, exception handling and other configurations
Thread.ofVirtual()
                .name("thread-test")
                .start(new SimpleThread());
//或者
Thread thread = Thread.ofVirtual()
  .name("thread-test")
  .uncaughtExceptionHandler((t, e) -> {
    
    
    System.out.println(t.getName() + e.getMessage());
  })
  .unstarted(new SimpleThread());
thread.start();
  1. Create threads using Factory
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(new SimpleThread());
thread.setName("thread-test");
thread.start();
  1. Using Executors
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
Future<?> submit = executorService.submit(new SimpleThread());
Object o = submit.get();

Examples of structured programming

Think about the following scenario. Suppose you have three tasks to be performed at the same time. As long as any one of the tasks is completed and returns a result, the result can be used directly, and the other two tasks can be stopped. For example, a weather service obtains weather conditions through three channels, as long as one channel returns it.
In this scenario, what should be done under Java 8, of course it is also possible.

// 执行任务并返回 Future 对象列表
List<Future<String>> futures = executor.invokeAll(tasks);

// 等待任一任务完成并获取结果
String result = executor.invokeAny(tasks);

Use the invokeAll and invokeAny implementations of ExecutorService, but there will be some extra work. After getting the first result, you need to manually close another thread.

In JDK21, it can be implemented with structured programming.
ShutdownOnSuccess captures the first result and closes the task scope to interrupt outstanding threads and wake up the calling thread.
A case where the results of any subtask are available directly without waiting for the results of other outstanding tasks.
It defines methods to get the first result or throw an exception if all subtasks fail

public static void main(String[] args) throws IOException {
    
    
  try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
    
    
    Future<String> res1 = scope.fork(() -> runTask(1));
    Future<String> res2 = scope.fork(() -> runTask(2));
    Future<String> res3 = scope.fork(() -> runTask(3));
    scope.join();
    System.out.println("scope:" + scope.result());
  } catch (ExecutionException | InterruptedException e) {
    
    
    throw new RuntimeException(e);
  }
}

public static String runTask(int i) throws InterruptedException {
    
    
  Thread.sleep(1000);
  long l = new Random().nextLong();
  String s = String.valueOf(l);
  System.out.println("第" + i + "个任务:" + s);
  return s;
}

ShutdownOnFailure
executes multiple tasks, as long as one fails (an exception occurs or other active exceptions are thrown), other unfinished tasks are stopped, and scope.throwIfFailed is used to catch and throw exceptions.
If all tasks are OK, use Feture.get() or *Feture.resultNow() to get the result

public static void main(String[] args) throws IOException {
    
    
  try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    
    
    Future<String> res1 = scope.fork(() -> runTaskWithException(1));
    Future<String> res2 = scope.fork(() -> runTaskWithException(2));
    Future<String> res3 = scope.fork(() -> runTaskWithException(3));
    scope.join();
    scope.throwIfFailed(Exception::new);

    String s = res1.resultNow(); //或 res1.get()
    System.out.println(s);
    String result = Stream.of(res1, res2,res3)
      .map(Future::resultNow)
      .collect(Collectors.joining());
    System.out.println("直接结果:" + result);
  } catch (Exception e) {
    
    
    e.printStackTrace();
    //throw new RuntimeException(e);
  }
}

// 有一定几率发生异常
public static String runTaskWithException(int i) throws InterruptedException {
    
    
  Thread.sleep(1000);
  long l = new Random().nextLong(3);
  if (l == 0) {
    
    
    throw new InterruptedException();
  }
  String s = String.valueOf(l);
  System.out.println("第" + i + "个任务:" + s);
  return s;
}

Examples of Scoped Values

We must have used ThreadLocal, which is a thread local variable, as long as the thread is not destroyed, the variable value in ThredLocal can be obtained at any time. Scoped Values ​​can also obtain variables at any time inside the thread, but it has a concept of scope, and it will be destroyed when it exceeds the scope.

public class ScopedValueExample {
    
    
    final static ScopedValue<String> LoginUser = ScopedValue.newInstance();

    public static void main(String[] args) throws InterruptedException {
    
    
        ScopedValue.where(LoginUser, "张三")
                .run(() -> {
    
    
                    new Service().login();
                });

        Thread.sleep(2000);
    }

    static class Service {
    
    
        void login(){
    
    
            System.out.println("当前登录用户是:" + LoginUser.get());
        }
    }
}

The above example simulates a user login process, uses ScopedValue.newInstance() to declare a ScopedValue, uses ScopedValue.where to set a value for the ScopedValue, and uses the run method to execute the next thing to be done, so that the ScopedValue is in The inside of run() can be obtained at any time. In the run method, the login method of a service is simulated. Without passing the parameter LoginUser, the value of the currently logged in user can be obtained directly through the LoginUser.get method.

Guess you like

Origin blog.csdn.net/u011397981/article/details/131277287