Java线程池的使用

频繁的创建和销毁线程需要花费大量时间,Java支持创建线程池,将多任务交给一组线程执行,提高执行效率。

1、创建线程池

Java标准库提供 ExecutorService 接口表示线程池,实现类有FixedThreadPool、CachedThreadPool、SingleThreadExecutor;创建线程池的方法被封装到Executors类。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(6);
        for (int i = 0; i < 10; i++) {
            es.submit(new Task(i));
        }
        es.shutdown();
    }
}

class Task implements Runnable {
    private int count = 0;
    Task(int count) {
        this.count = count;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.count);
    }
}
// 2
// 4
// 1
// 5
// 3
// 0

// 8
// 6
// 9
// 7

由于FixedThreadPool 总是创建固定的线程数量,当一次性提交的任务数量超过线程池中线程的数时,多出的任务需要等待。总结:

  • FixedThreadPool:创建指定数量线程的线程池;
  • CachedThreadPool:根据任务动态调整线程数的线程池,同时支持创建线程动态范围的线程池;
  • SingleThreadExecutor:单线程执行的线程池。

线程池结束方式:

  • es.shutdown() :等待任务完成后,关闭线程池;
  • es.shutdownNow(): 立即结束任务,并关闭线程池;
  • es.awaitTermination(): 指定等待时间后,关闭线程池。

2、ScheduledThreadPool

ScheduledThreadPool 重复执行任务的线程池,Java标准库使用 ScheduledExecutorService 表示接口,创建方法同样被封装到Executors中。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPool {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
ses.schedule(
new ScheduledTask(), 1, TimeUnit.SECONDS); ses.scheduleAtFixedRate(new ScheduledTask(), 0, 1, TimeUnit.SECONDS);      //ses.awaitTermination(10, TimeUnit.SECONDS); Thread.sleep(10000); ses.shutdownNow(); } } class ScheduledTask implements Runnable { private int i = 0; @Override public void run() { System.out.println(i++); } }

其中:

  • ses.schedule(new ScheduledTask(), 1, TimeUnit.SECONDS): 表示1秒后执行任务;
  • ses.scheduleAtFixedRate(new ScheduledTask(), 0, 1, TimeUnit.SECONDS):表示0秒后,每 1 秒执行一次;
  • ses.scheduleWithFixedDelay(new ScheduledTask(), 0, 1, TimeUnit.SECONDS):表示0秒后,每间隔 1 秒执行一次;

3、Future对象

使用线程池可以方便的处理多任务,只需要任务实现Runable接口,调用submit方法提交执行即可。但缺点是无法返回值,Java提供Future对象返回执行结果,但异步任务需要实现Callable接口。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class ThreadPool {

    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executor = Executors.newFixedThreadPool(4); 
        Callable<String> task = new Task();
        Future<String> future = executor.submit(task);
        String res = future.get(1, TimeUnit.SECONDS);
        System.out.println(res);
    }
}

class Task implements Callable<String>{ public String call() throws InterruptedException { Thread.sleep(2000); //模拟耗时任务 return "done"; } }

可以看到 Callable接口是泛型接口,可以指定返回类型。需要注意 future对象:

  • future.get()直接调用可能会阻塞当前线程;
  • future.get(long time, TimeUnit unit)可指定等待时间,超时会报timeoutException;
  • future.cancel()取消任务;
  • future.isDone(),判断任务是否完成。

 4、CompletableFuture

 使用Future获取异步执行的结果或阻塞当前线程,Java 8引入的CompletableFuture,支持异步回调,其用法类似JavaScript中的Promise对象的用法。

import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

public class ThreadPool {
    public static void main(String[] args) throws InterruptedException {
        // 异步执行
        CompletableFuture<String> apple = CompletableFuture.supplyAsync(new FetchApple());
        apple.thenAccept((result) -> {
            System.out.println(result);
        });
        apple.exceptionally((e) -> {
            e.printStackTrace();
            return null;
        });
        Thread.sleep(3000);
    }
}

class FetchApple implements Supplier<String> {
    @Override
    public String get() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Apple";
    }
}

CompletableFuture支持传入成功和失败的回调,在将来的时刻执行,并且不会阻塞当前线程。同时 CompletableFuture支持类似于Promise的链式回调,Promise.race的竞争并发执行和类似Promise.all的全部执行,但是用法上会复杂许多。CompletableFuture会使用默认线程池

由于Java的严谨性,CompletableFuture.supplyAsync传入的对象需要实现Supplier<T>接口、成功的回调thenAccept传入对象需要实现Consumer<T>接口、失败的回调exceptionally需要传入实现Function<T,R>接口的对象。但好在支持的lambda语法可以进行简化。

 三者对比

  对象实现接口 覆写方法 @Override 问题
线程池 Runnable run 无法返回值
Future<V> Callable<V> call get方法阻塞
CompletableFuture<V> Supplier<T> get 异步接收对象需要实现Consumer<T>接口,异常需要实现Function<T, R> 接口。

 

 

 

参考链接:

https://www.liaoxuefeng.com/wiki/1252599548343744/1306581155184674 

posted @ 2021-11-17 15:54  恩恩先生  阅读(342)  评论(0编辑  收藏  举报