Browse Source
VirtualThreadDelegate built on JDK 21 for multi-release jar. Includes dedicated VirtualThreadTaskExecutor as lean option. Includes setVirtualThreads flag on SimpleAsyncTaskExecutor. Includes additional default methods on AsyncTaskExecutor. Closes gh-30241pull/30456/head
Juergen Hoeller
1 year ago
16 changed files with 385 additions and 64 deletions
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.core.task; |
||||
|
||||
import java.util.concurrent.ThreadFactory; |
||||
|
||||
/** |
||||
* Internal delegate for virtual thread handling on JDK 21. |
||||
* This is a dummy version for reachability on JDK <21. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
* @see VirtualThreadTaskExecutor |
||||
*/ |
||||
class VirtualThreadDelegate { |
||||
|
||||
public VirtualThreadDelegate() { |
||||
throw new UnsupportedOperationException("Virtual threads not supported on JDK <21"); |
||||
} |
||||
|
||||
public ThreadFactory virtualThreadFactory() { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
public ThreadFactory virtualThreadFactory(String threadNamePrefix) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
public Thread startVirtualThread(String name, Runnable task) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.core.task; |
||||
|
||||
import java.util.concurrent.ThreadFactory; |
||||
|
||||
/** |
||||
* A {@link TaskExecutor} implementation based on virtual threads in JDK 21+. |
||||
* The only configuration option is a thread name prefix. |
||||
* |
||||
* <p>For additional features such as concurrency limiting or task decoration, |
||||
* consider using {@link SimpleAsyncTaskExecutor#setVirtualThreads} instead. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
* @see SimpleAsyncTaskExecutor |
||||
*/ |
||||
public class VirtualThreadTaskExecutor implements AsyncTaskExecutor { |
||||
|
||||
private final ThreadFactory virtualThreadFactory; |
||||
|
||||
|
||||
/** |
||||
* Create a new {@code VirtualThreadTaskExecutor} without thread naming. |
||||
*/ |
||||
public VirtualThreadTaskExecutor() { |
||||
this.virtualThreadFactory = new VirtualThreadDelegate().virtualThreadFactory(); |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@code VirtualThreadTaskExecutor} with thread names based |
||||
* on the given thread name prefix followed by a counter (e.g. "test-0"). |
||||
* @param threadNamePrefix the prefix for thread names (e.g. "test-") |
||||
*/ |
||||
public VirtualThreadTaskExecutor(String threadNamePrefix) { |
||||
this.virtualThreadFactory = new VirtualThreadDelegate().virtualThreadFactory(threadNamePrefix); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Return the underlying virtual {@link ThreadFactory}. |
||||
* Can also be used for custom thread creation elsewhere. |
||||
*/ |
||||
public final ThreadFactory getVirtualThreadFactory() { |
||||
return this.virtualThreadFactory; |
||||
} |
||||
|
||||
@Override |
||||
public void execute(Runnable task) { |
||||
this.virtualThreadFactory.newThread(task).start(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.core.task; |
||||
|
||||
import java.util.concurrent.ThreadFactory; |
||||
|
||||
/** |
||||
* Internal delegate for virtual thread handling on JDK 21. |
||||
* This is the actual version compiled against JDK 21. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
* @see VirtualThreadTaskExecutor |
||||
*/ |
||||
class VirtualThreadDelegate { |
||||
|
||||
private final Thread.Builder threadBuilder = Thread.ofVirtual(); |
||||
|
||||
public ThreadFactory virtualThreadFactory() { |
||||
return this.threadBuilder.factory(); |
||||
} |
||||
|
||||
public ThreadFactory virtualThreadFactory(String threadNamePrefix) { |
||||
return this.threadBuilder.name(threadNamePrefix, 0).factory(); |
||||
} |
||||
|
||||
public Thread startVirtualThread(String name, Runnable task) { |
||||
return this.threadBuilder.name(name).start(task); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.core.task; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
*/ |
||||
class VirtualThreadTaskExecutorTests { |
||||
|
||||
@Test |
||||
void virtualThreadsWithoutName() { |
||||
final Object monitor = new Object(); |
||||
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor(); |
||||
ThreadNameHarvester task = new ThreadNameHarvester(monitor); |
||||
executeAndWait(executor, task, monitor); |
||||
assertThat(task.getThreadName()).isEmpty(); |
||||
assertThat(task.isVirtual()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void virtualThreadsWithNamePrefix() { |
||||
final Object monitor = new Object(); |
||||
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("test-"); |
||||
ThreadNameHarvester task = new ThreadNameHarvester(monitor); |
||||
executeAndWait(executor, task, monitor); |
||||
assertThat(task.getThreadName()).isEqualTo("test-0"); |
||||
assertThat(task.isVirtual()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void simpleWithVirtualThreadFactory() { |
||||
final Object monitor = new Object(); |
||||
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(Thread.ofVirtual().name("test").factory()); |
||||
ThreadNameHarvester task = new ThreadNameHarvester(monitor); |
||||
executeAndWait(executor, task, monitor); |
||||
assertThat(task.getThreadName()).isEqualTo("test"); |
||||
assertThat(task.isVirtual()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void simpleWithVirtualThreadFlag() { |
||||
final String customPrefix = "chankPop#"; |
||||
final Object monitor = new Object(); |
||||
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(customPrefix); |
||||
executor.setVirtualThreads(true); |
||||
ThreadNameHarvester task = new ThreadNameHarvester(monitor); |
||||
executeAndWait(executor, task, monitor); |
||||
assertThat(task.getThreadName()).startsWith(customPrefix); |
||||
assertThat(task.isVirtual()).isTrue(); |
||||
} |
||||
|
||||
private void executeAndWait(TaskExecutor executor, Runnable task, Object monitor) { |
||||
synchronized (monitor) { |
||||
executor.execute(task); |
||||
try { |
||||
monitor.wait(); |
||||
} |
||||
catch (InterruptedException ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
private static final class NoOpRunnable implements Runnable { |
||||
|
||||
@Override |
||||
public void run() { |
||||
// no-op
|
||||
} |
||||
} |
||||
|
||||
|
||||
private static abstract class AbstractNotifyingRunnable implements Runnable { |
||||
|
||||
private final Object monitor; |
||||
|
||||
protected AbstractNotifyingRunnable(Object monitor) { |
||||
this.monitor = monitor; |
||||
} |
||||
|
||||
@Override |
||||
public final void run() { |
||||
synchronized (this.monitor) { |
||||
try { |
||||
doRun(); |
||||
} |
||||
finally { |
||||
this.monitor.notifyAll(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
protected abstract void doRun(); |
||||
} |
||||
|
||||
|
||||
private static final class ThreadNameHarvester extends AbstractNotifyingRunnable { |
||||
|
||||
private String threadName; |
||||
|
||||
private boolean virtual; |
||||
|
||||
protected ThreadNameHarvester(Object monitor) { |
||||
super(monitor); |
||||
} |
||||
|
||||
public String getThreadName() { |
||||
return this.threadName; |
||||
} |
||||
|
||||
public boolean isVirtual() { |
||||
return this.virtual; |
||||
} |
||||
|
||||
@Override |
||||
protected void doRun() { |
||||
Thread thread = Thread.currentThread(); |
||||
this.threadName = thread.getName(); |
||||
this.virtual = thread.isVirtual(); |
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue