java-3.7 juc-thread

136 阅读3分钟

线程

java.lang.Thread/Runnable

  • 一般创建线程只有两种方式,一种是继承Thread,一种是实现Runnable接口。但是这两种创建方式没有返回值
  • 一般不提倡使用继承Thread来创建线程方式,因为Java只有单继承,不能继承多个。
  • 之后要上线程池,如果是用Runnable来实现的,那就可以直接传入Runnable给线程池进行管理了

Callable

Callable也是一个接口,很简单就一个call方法返回的就是执行的结果了。和Runnable 差别就是它有返回的结果,并且可以抛出异常!

public interface Callable<V> {
   V call() throws Exception;
}

配合ThreadPoolExecutor使用的, 不能直接配合Thread 使用。

   Callable<String> callable =
        new Callable<String>() {
          @Override
          public String call() throws Exception {
            return "hello";
          }
        };
    ExecutorService executor = Executors.newFixedThreadPool(1);
    Future<String> result = executor.submit(callable);
    System.out.println(result.get());

Future

Future也是一个接口,它可以对具体的Runnable或者Callable任务进行取消、判断任务是否已取消、查询任务是否完成、获取任务结果。 如果是Runnable的话返回的结果是null。

接口里面有以下几个方法。注意两个get方法都会阻塞当前调用get的线程,直到返回结果或者超时才会唤醒当前的线程。

public interface Future<V> {
  boolean cancel(boolean mayInterruptIfRunning);  // 取消任务
  boolean isCancelled();  // 判断任务是否已取消  
  boolean isDone(); // 判断任务是否已结束
  V get() throws InterruptedException, ExecutionException;// 获得任务执行结果
  V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;// 获得任务执行结果,支持超时
}

FutureTask

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。 FutureTask是个class。它实现了RunnableFuture接口public class FutureTask<V> implements RunnableFuture<V> 而RunnableFuture接口又继承了Runnable和Future 因此它可以作为Runnable被线程执行,又可以有Future的那些操作。

FutureTask<String> futureTask = new FutureTask<>(() -> "hello");
Thread T1 = new Thread(futureTask);
T1.start();
String result = futureTask.get();
System.out.println(result);

线程池

线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

形象比喻:

image.png

执行流程:

image.png

handler 线程池的饱和策略:

image.png

  • AbortPolicy 直接抛出异常,默认策略;
  • CallerRunsPolicy 用调用者所在的线程来执行任务;
  • DiscardOldestPolicy 丢弃阻塞队列中第一个(即最旧)的任务,并执行当前任务;
  • DiscardPolicy 直接丢弃任务。

线程池不建议使用工具类Executors创建

线程池不允许使用工具类Executors创建,而是通过ThreadPoolExecutor的方式,这样的处理方式可以更加明确线程池的运行规则,规避资源耗尽的风险 Executors返回的线程池弊端如下:

  1. newFixedThreadPool和:newSingleThreadExecutor

允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

  1. newCachedThreadPool 允许的创建线程数量为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
final static ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

Fork/Join

参考

tech.meituan.com/2020/04/02/…