我正在开发一个Java程序,它使用虚拟线程来执行特定任务(与jssc的串行通信程序)。我想知道是否有办法根据用户输入暂停和恢复这些虚拟线程。我读过一些关于“继续”的内容,但它似乎还无法使用。
例如,我希望当用户在程序界面上按下“暂停”按钮时暂停程序,并在用户单击“播放”按钮时恢复程序。
我正在使用 Java 21,目前没有使用任何特定的库或框架进行线程管理。
提前谢谢您!
虚拟线程在锁和信号量方面的行为相同。因此,您可以对虚拟线程使用与平台线程相同的方法。
引用JEP 444:
支持锁定的原始 API java.util.concurrent.LockSupport 现在支持虚拟线程……使所有使用它的 API(锁、信号量、阻塞队列等)在虚拟线程中调用时能够优雅地停放。
但是,您可能会错过虚拟线程的好处。
他们发明的原因是提供廉价线程,这意味着对内存、CPU、JVM 或主机操作系统的影响很小。虚拟线程就像面巾纸:需要时,拿一张新的、使用并丢弃。因此,与其暂停和恢复虚拟线程,不如考虑仅处理当前虚拟线程,然后在需要时启动另一个虚拟线程。
这里是一个示例应用程序,它就是这样做的。启动后,该应用程序每秒发出一次蜂鸣声。在控制台上,用户可以暂停蜂鸣声,并恢复蜂鸣声。或者用户这么认为。暂停确实让当前发出声音的虚拟线程结束运行并死亡。恢复实际上启动了一个新的虚拟线程,以每秒蜂鸣声重新开始。
在现代 Java 中,我们很少需要直接处理
Thread
类。通常最好让执行程序服务来处理线程。这里我们选择使用由虚拟线程支持的执行器服务。
请注意,执行者服务是
AutoCloseable
。这意味着我们可以使用 try-with-resources 语法来自动结束执行器服务。
我们在这里使用线程安全的集合来充当日志,在应用程序执行期间收集反馈。这是完成的,而不是直接调用
System.out.println
,因为这些调用的输出在跨线程时不一定按时间顺序出现在控制台上。
package work.basil.example.threading;
import java.time.Duration;
import java.time.Instant;
import java.util.Scanner;
import java.util.SequencedCollection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
public class Pausing
{
static final SequencedCollection < String > log = new CopyOnWriteArrayList <>( );
public static void main ( String[] args )
{
Pausing app = new Pausing( );
app.demo( );
}
private void demo ( )
{
log.add( "INFO - starting `demo` method at " + Instant.now( ) + " in thread id: " + Thread.currentThread( ).threadId( ) + " thread name: " + Thread.currentThread( ).getName( ) );
final AtomicReference < BeepingCondition > beepingStatus = new AtomicReference <>( BeepingCondition.BEEPING );
try (
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor( ) ;
)
{
Future < ? > beepingFuture = executorService.submit( new Beeper( beepingStatus ) );
Scanner scanner = new Scanner( System.in );
System.out.println( "Beeper demo has commenced. Hear a beep every second." );
while ( beepingStatus.get( ) != BeepingCondition.ENDED )
{
System.out.println( "To pause the beeping, type 'pause'. To restart beeping, 'beep'. To end this program, 'stop'. " );
String input = scanner.nextLine( ).toLowerCase( ); // Blocks here until a line sent by user.
log.add( "INFO - User intered input into `demo` method at " + Instant.now( ) + " in thread id: " + Thread.currentThread( ).threadId( ) + " thread name: " + Thread.currentThread( ).getName( ) + " Input: " + input );
switch ( input )
{
case "beep" ->
{
if ( beepingStatus.get( ) == BeepingCondition.BEEPING )
{
// Do nothing. Already beeping.
} else // Else not currently beeping. Start beeping again.
{
beepingFuture = executorService.submit( new Beeper( beepingStatus ) );
beepingStatus.set( BeepingCondition.BEEPING );
}
}
case "pause" ->
{
if ( beepingStatus.get( ) == BeepingCondition.PAUSED )
{
// Do nothing. Already paused.
} else // Else not currently beeping. Start beeping again.
{
beepingFuture.cancel( true );
beepingStatus.set( BeepingCondition.PAUSED );
}
}
case "stop" ->
{
beepingFuture.cancel( true );
beepingStatus.set( BeepingCondition.ENDED );
}
default ->
{
System.out.println( "Your input is unexpected. Try again. " );
}
}
}
}
log.add( "INFO - ending `demo` method at " + Instant.now( ) + " in thread id: " + Thread.currentThread( ).threadId( ) + " thread name: " + Thread.currentThread( ).getName( ) );
log.forEach( System.out :: println );
}
static class Beeper implements Runnable
{
private final AtomicReference < BeepingCondition > keepOnBeeping;
public Beeper ( final AtomicReference < BeepingCondition > continueBeeping )
{
this.keepOnBeeping = continueBeeping;
}
@Override
public void run ( )
{
log.add( "INFO - starting `run` method at " + Instant.now( ) + " in thread id: " + Thread.currentThread( ).threadId( ) + " thread name: " + Thread.currentThread( ).getName( ) );
while ( this.keepOnBeeping.get( ) != BeepingCondition.ENDED )
{
java.awt.Toolkit.getDefaultToolkit( ).beep( );
try { Thread.sleep( Duration.ofSeconds( 1 ) ); } catch ( InterruptedException e )
{
log.add( "INFO - `run` method interrupted at " + Instant.now( ) + " in thread id: " + Thread.currentThread( ).threadId( ) + " thread name: " + Thread.currentThread( ).getName( ) );
break; // Bail out of this `while` loop.
}
}
log.add( "INFO - ending `run` method at " + Instant.now( ) + " in thread id: " + Thread.currentThread( ).threadId( ) + " thread name: " + Thread.currentThread( ).getName( ) );
}
}
enum BeepingCondition
{ BEEPING, PAUSED, ENDED }
}