我没有使用 ScheduledExecutorService。我想要简单的方法来解决这个问题。
您已经有了答案:将 Executors 框架添加到 Java 中是为了解决这个问题的简单方法。执行器抽象出处理后台任务和线程的棘手杂乱细节。
您应该使用ScheduledExecutorService 来达到您的目的。
Executors 乍一看可能让人望而生畏,但在实践中它们的使用却非常简单。阅读 Oracle 教程。然后看看 Stack Overflow 和其他博客等中的代码示例。
提示:使用外部搜索引擎(例如 DuckDuckGo/Bing/Google 和 site:StackOverflow.com 标准)搜索 Stack Overflow。 Stack Overflow 中的内置搜索功能乏善可陈,并且偏向于问题而不是答案。
定义由线程池支持的执行器服务。
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
将您的任务定义为Runnable。
Runnable task = new Runnable() {
@Override
public void run () {
… stuff to do goes here
}
}
将Runnable 对象传递给执行器服务。告诉它多久运行一次。您还通过了初始延迟;这里我们通过零立即开始。
scheduledExecutorService.scheduleAtFixedRate( task , 0 , 2 , TimeUnit.HOURS );
提示:关于使用 ScheduledExecutorService (SES) 需要了解的两个重要事项:
- 在不再需要或应用退出时始终优雅地关闭 SES。否则线程池可能会存活下来并继续工作,就像僵尸一样。
- 始终捕获提交给 SES 的任务中的所有异常,可能还有错误。如果有任何异常或错误冒泡到达 SES,则 SES 的所有进一步工作都会增加。停止是无声的,没有警告,没有日志。
所以修改上面看到的Runnable。
Runnable task = new Runnable() {
@Override
public void run () {
try {
… stuff to do goes here
} catch ( Exception e ) {
… Handle any unexpected exceptions bubbling up to avoid silently killing your executor service.
}
}
}
提示:切勿使用糟糕的旧日期时间类,例如 Date、Calendar、SimpleDateFormat。它们在几年前被现代 java.time 类所取代。
提示:在文件名中写入时刻时,请遵循ISO 8601 标准将日期时间值表示为文本。
java.time 类在解析或生成字符串时默认使用这些格式。
对于文件名,您可能希望使用标准中定义的替代“基本”格式。 基本表示将分隔符的使用减到最少。
请务必避免使用反斜杠、正斜杠和冒号字符。这些分别在 DOS/Windows、Unix 和 macOS/iOS 文件系统中被禁止。
提示:不要编写自己的 CSV 代码。使用 Apache Commons CSV 库读取和写入 CSV 或制表符分隔的文件。根据我的经验,它运作良好。
示例应用
这是一个包含在单个 .java 文件中的完整示例。
我们的小班级Event 有一些对象。在每次计划运行时,我们都会更新每个 Event 对象的时间戳。然后我们使用 Apache Commons CSV 库将所有这些 Event 对象的所有成员变量写入 CSV 格式的文本文件。每次运行都由 Runnable 对象定义。
运行由ScheduledExecutorService 调度,由具有单个线程的线程池支持。
在实际工作中,我不会将所有这些压缩到一个 .java 文件中。但是在这里这样做可以得到一个很好的紧凑演示。
package com.basilbourque.example;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ExportToCsv {
public static void main ( String[] args ) {
ExportToCsv app = new ExportToCsv();
app.doIt();
}
private void doIt () {
System.out.println( "TRACE - doIt running at " + ZonedDateTime.now() );
List< Event > events = List.of(
new Event( UUID.randomUUID() , "alpha" , Instant.now() ) ,
new Event( UUID.randomUUID() , "beta" , Instant.now() ) ,
new Event( UUID.randomUUID() , "gamma" , Instant.now() )
);
Runnable task = new Runnable() {
@Override
public void run () {
// Nest all this stuff of your `run` method into a `try-catch( Exception e )` to avoid having your executor cease silently.
Instant start = Instant.now();
System.out.print( "TRACE - Runnable starting at " + start + ". " );
// Update the moment recorded in each `Event` object.
events.forEach( ( event ) -> event.update() );
// Export to CSV. Using “Apache Commons CSV” library. https://commons.apache.org/proper/commons-csv/
// Get current moment in UTC. Lop off the seconds and fractional second. Generate text without delimiters.
String dateTimeLabel = OffsetDateTime.now( ZoneOffset.UTC ).truncatedTo( ChronoUnit.MINUTES ).format( DateTimeFormatter.ofPattern( "uuuuMMdd'T'HHmmX" , Locale.US ) );
String fileNamePath = "myCsv_" + dateTimeLabel + ".csv";
try ( // Try-with-resources syntax automatically closes any passed objects implementing `AutoCloseable`, even if an exception is thrown.
BufferedWriter writer = new BufferedWriter( new FileWriter( fileNamePath ) ) ;
CSVPrinter csvPrinter = new CSVPrinter( writer , CSVFormat.DEFAULT.withHeader( "Id" , "Name" , "When" ) ) ;
) {
for ( Event event : events ) {
csvPrinter.printRecord( event.id , event.name , event.when );
}
csvPrinter.flush();
} catch ( IOException e ) {
// TODO: Handle i/o exception when creating or writing to file in storage.
e.printStackTrace();
}
Instant stop = Instant.now() ;
System.out.println( "Runnable ending its run at " + start + ". Duration: " + Duration.between( start , stop ) + ".");
}
};
// Schedule this task. Currently set to run every two minutes, ending after 20 minutes. Adjust as desired.
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); // Using a single thread here, as we have only a single series of tasks to be executed, no multi-tasking.
try {
scheduledExecutorService.scheduleAtFixedRate( task , 0 , 2 , TimeUnit.MINUTES ); // Schedule our task to run every so often.
try {
Thread.sleep( TimeUnit.MINUTES.toMillis( 20 ) ); // Sleep this main thread for a while to let our task running on the background thread do its thing a few times.
} catch ( InterruptedException e ) {
System.out.println( "TRACE - Our main thread was woken earlier than expected, and interrupted our run. " );
e.printStackTrace();
}
} finally {
System.out.println( "Shutting down the scheduledExecutorService at " + ZonedDateTime.now() ); // Generally best to log in UTC, `Instant.now()`.
scheduledExecutorService.shutdown(); // Always shutdown your executor, as it may otherwise survive your app exiting, becoming a zombie, continuing to run endlessly.
}
System.out.println( "App running on main thread ending at " + Instant.now() + "." );
}
class Event {
public UUID id;
public String name;
public Instant when;
public Event ( UUID id , String name , Instant when ) {
this.id = id;
this.name = name;
this.when = when;
}
public void update () {
this.when = Instant.now();
}
}
}
运行时。
TRACE - doIt 运行于 2018-09-24T20:16:25.794081-07:00[America/Los_Angeles]
TRACE - 可从 2018-09-25T03:16:25.832025Z 开始运行。 Runnable 在 2018-09-25T03:16:25.832025Z 结束运行。持续时间:PT0.025342S。
TRACE - 可从 2018-09-25T03:18:25.829634Z 开始运行。 Runnable 在 2018-09-25T03:18:25.829634Z 结束运行。持续时间:PT0.001121S。
此屏幕截图中显示的文件。