Threads and Executors

Threads and Runnables

Java自JDK1.0就支持线程,一般来说,在启动线程之前,我们需要指定线程实际执行方法体,通常我们可以通过重写Runnable接口中的run方法来完成。

Runnable task = () -> {
    String threadName = Thread.currentThread().getName();
    System.out.println("Hello " + threadName);
};

task.run();

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

System.out.println("Done!");

注意,Runnable是一个函数式接口(接口中有且只有一个抽象方法),所以我们可以使用Java 8中的lambda表达式来打印当前线程名称。执行结果如下:

Hello main
Hello Thread-0
Done!

或者

Hello main
Done!
Hello Thread-0

在多线程中,子程序与主程序执行顺序是不确定的,这就使得多线程编程在更为复杂的应用程序中的应用成为挑战。

在进行更深入的探讨前,我们先作一个简单的说明,这里我们使用线程休眠的方式来模拟后续章节中线程长时间执行的业务场景。

Runnable runnable = () -> {
    try {
        String name = Thread.currentThread().getName();
        System.out.println("Foo " + name);
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Bar " + name);
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
};

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

在JDK 1.5之前,多线程编程繁琐易错,JDK 1.5之后,引入了Concurrency API并发编程框架,框架包含了许多有用用以处理多线程编程的Java类。

Executors

public interface ExecutorService extends Executor

Executors以内部线程池的方式支持异步任务的调度。

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
    String threadName = Thread.currentThread().getName();
    System.out.println("Hello " + threadName);
});

注意:如果想要停止Executors服务,可以调用shutdown()或者shutdownNow()方法。

try {
    System.out.println("attempt to shutdown executor");
    executor.shutdown();
    executor.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
    System.err.println("tasks interrupted");
}
finally {
    if (!executor.isTerminated()) {
        System.err.println("cancel non-finished tasks");
    }
    executor.shutdownNow();
    System.out.println("shutdown finished");
}

Callables and Futures

Executors除支持Runnable接口类型的任务外,还支持Callable接口类型的任务,与Runnable接口类似,Callable也是函数式接口,区别是Callable有返回值,而Callable是没有返回值的。

Callable<Integer> task = () -> {
    try {
        TimeUnit.SECONDS.sleep(1);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
};

当我们调用ExecutorServicesubmit方法时,对得到一个Future对象,通过这个对象,我们就可以获得Callable类型任务实际执行结果。

Callable<Integer> task = () -> {
    try {
        TimeUnit.SECONDS.sleep(1);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
};
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(task);

System.out.println("future done? " + future.isDone());

Integer result = future.get();

System.out.println("future done? " + future.isDone());
System.out.print("result: " + result);

调用Future对象的get方法会阻塞当前线程直至callable对象执行完毕,并返回执行结果。

future done? false
future done? true
result: 123

Timeouts

如前所述,调用Future对象的get方法会阻塞当前线程。考虑最坏的情况,Callable对象一直无法退出,这将会导致应用程序本身无响应。所以,为了处理这种异常,我们可以传递一个时间参数。

ExecutorService executor = Executors.newFixedThreadPool(1);

Future<Integer> future = executor.submit(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
});

future.get(1, TimeUnit.SECONDS);

当超时时间到,自动抛出TimeoutException异常。

Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask.get(FutureTask.java:205)

InvokeAll

阻塞进程,直至返回所有的任务执行结果

ExecutorService executor = Executors.newWorkStealingPool();

List<Callable<String>> callables = Arrays.asList(
        () -> "task1",
        () -> "task2",
        () -> "task3");

executor.invokeAll(callables)
    .stream()
    .map(future -> {
        try {
            return future.get();
        }
        catch (Exception e) {
            throw new IllegalStateException(e);
        }
    })
    .forEach(System.out::println);

InvokeAny

阻塞进程,直至返回第一个执行完毕的任务结果

Callable<String> callable(String result, long sleepSeconds) {
    return () -> {
        TimeUnit.SECONDS.sleep(sleepSeconds);
        return result;
    };
}

ExecutorService executor = Executors.newWorkStealingPool();

List<Callable<String>> callables = Arrays.asList(
    callable("task1", 2),
    callable("task2", 1),
    callable("task3", 3));

String result = executor.invokeAny(callables);
System.out.println(result);

// => task2

上述例子使用了Java 8 引入的新的工厂方法newWorkStealingPool(),newWorkStealingPool()返回的是ForkJoinPool,与正常的Executor略有不通,ForkJoinPool会根据主机CPU返回来创建包含给定数目线程的线程池。

Scheduled Executors

ScheduledExecutorService定期任务执行。

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
ScheduledFuture<?> future = executor.schedule(task, 3, TimeUnit.SECONDS);

TimeUnit.MILLISECONDS.sleep(1337);

long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS);
System.out.printf("Remaining Delay: %sms", remainingDelay);

schedule返回的是ScheduledFuture对象,除schedule方法外,ScheduledExecutorService还提供另外两种方法:

  • scheduleAtFixedRate()
  • scheduleWithFixedDelay()
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());

int initialDelay = 0;
int period = 1;
executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);

注意:scheduleAtFixedRate()方法并不考虑实际任务持续时间,比如,如果任务本身执行持续时间需要2秒,但你却为scheduleAtFixedRate()执行每1秒执行一次,那么线程池中的线程将很快用完,这种情况下,你可以考虑使用scheduleWithFixedDelay()方法来替代。

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Scheduling: " + System.nanoTime());
    }
    catch (InterruptedException e) {
        System.err.println("task interrupted");
    }
};

executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);

https://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-examples/

猜你喜欢

转载自blog.csdn.net/kangkanglou/article/details/82348478