使用 Runnable、 Callable 创建工作线程,并使用 ExecutorService 并发执行任务。确定死锁、饥饿、活锁和竞争条件中的潜在线程处理问题。

线程

简单地理解,并发意味着同时并行地做事情。
Java中并发是通过线程来完成的
线程是可以同时执行的代码单元。它们有时被称为轻量级进程,但实际上,线程是在进程中执行的(每个进程至少有一个线程,即主线程)。

创建线程

两种方法创建线程

Runable(推荐)

image.png
其为函数式接口可以使用lambda

  • 1.实现 java.lang.Runnable 接口
class RunnableTask implements Runnable {
    public void run() {
        System.out.println("Running");
    }
}
  • 2.将一个实例传递给java.lang.Thread
    的构造函数。
    线程类并请求线程启动(它可能不会立即启动)。此外,执行线程的顺序和时间并不保证)
Thread thread = new Thread(new RunnableTask());
thread.start();

或者通过匿名内部类

Thread thread = new Thread(new RunnableTask() {
    public void run() {
        System.out.println("Running");
    }
 });
thread.start();

或者lamdba

Thread thread = new Thread(() -> {
    System.out.println("Running");
});
thread.start();

Thread(不推荐)

  • 1.重写run方法
class ThreadTask extends Thread {
    public void run() {
        System.out.println("Running");
    }
}
  • 2.创建实例调用start方法
Thread thread = new ThreadTask();
thread.start();

实现 Runnable是推荐的方法
因为有了新的并发 API,不必再直接创建 Thread 对象(实现接口也是推荐的面向对象的方法)。

ExecutorService

44a7cc359b8ffe33924d9f9a59be68e.png
Java5引入了一个高级的并发 API,其中大部分是在 Java.util.concurrent 包中实现的
image.png

  • Executor 接口,它提供了启动和管理线程的替代(更好的)方法
    说到这推荐一本书,这本书的作者就是JUC包的作者,可想而知含金量
    image.png
    image.png

Executor接口

image.png
只有execute()方法,

  • 执行线程
Runnable r = ...
Executor e = ...
e.execute(r);

用于替换

Runnable r = ...
Thread t = new Thread(r);
t.start();

ExecutorService接口

继承Executor,以提供更多的特性,比如 submit ()方法,它接受 Runnable 和 Callable 对象,并允许它们返回值
image.png

ScheduledExecutorService接口

继承ExecutorService,以重复的间隔或特定的延迟执行任务。
image.png

线程池

Executors使用线程池。这些线程与使用 Thread 类创建的线程不同。
当创建工作线程时,它们只是站在空闲位置,等待工作。当工作到达时,执行程序将它分配给线程池中的空闲线程。
线程是通用的,它们独立于它们所执行的 Runnable 任务(与使用 Thread 类创建的传统线程相反)。
线程池的一种类型是固定线程池,它有固定数量的线程在运行。如果一个线程在使用期间终止,它将自动被一个新线程替换。还有可扩展的线程池。

Executors

f605b6b23cf55f6e0f981889ac376dd.png
一般加s的都是工具类,且提供的大多为static方法,供调度使用。例如Collections、Paths、Executors
ExecutorService比 Executor 具有更多的功能。因为它们是接口,创建 Executor 的实例,必须使用java.util.concurrent.Executors提供的方法
image.png

Executors创建ExecutorService的静态static方法

  • 创建一个使用单个工作线程的 Executor
static ExecutorService newSingleThreadExecutor()

image.png

  • 创建一个线程池,该线程池重用固定数量的线程
static ExecutorService
        newFixedThreadPool(int nThreads)

image.png

  • 创建一个线程池,可以调度任务
static ScheduledExecutorService
        newScheduledThreadPool(int corePoolSize)

image.png

有一个线程的ExecutorService

        while (true) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Runnable r = () -> {
                IntStream.rangeClosed(1, 4)
                        .forEach(System.out::println);
            };
            System.out.println("before executing");
            executor.execute(r);
            System.out.println("after executing");
            executor.shutdown();
        }

image.png

关闭线程

使用 ExecutorService 之后,必须关闭它以终止线程并关闭资源。否则会OOM,线程池的数量是有最大值的和具体的JVM参数和机器内存等有关
image.png

  • 关闭方法有2种
    image.png
void shutdown()

Shutdown ()方法告诉执行程序停止接受新任务,但是允许以前的任务继续执行,直到完成。
在此期间,isTerminated ()方法将返回 false,直到所有任务完成,而 isShutdown ()方法将始终返回 true。
2.

List<Runnable> shutdownNow()

shutdownNow ()方法也会告诉执行者停止接受新任务,但是它会尝试立即停止所有执行任务(通过中断线程,但是如果线程没有响应中断,它可能永远不会终止) ,并返回一个从未启动的任务列表。

ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable r = () -> {
    try {
        Thread.sleep(5_000);
    } catch (InterruptedException e) {
        System.out.println("Interrupted");
    }
};
executor.execute(r);
executor.shutdownNow();

image.png
Interrupted被打印出来,程序将立即终止
image.png

Executors返回Future对象

Executors类的静态static内部类DelegatedExecutorService有返回Future对象的方法
image.png

  • 执行 Runnable 并返回表示该任务的 Future
Future<?> submit(Runnable task)
  • 执行一个 Callable 并返回一个表示任务结果的 Future。
<T> Future<T> submit(Callable<T> task)
  • 执行给定的任务,返回已完成的任务的结果,如果有异常,则不抛出异常。其他的任务被取消
<T> T invokeAny(
       Collection<? extends Callable<T>> tasks)
   throws InterruptedException, ExecutionException
  • 执行给定的任务,返回一个Future的List,其中包含所有完成时的状态和结果(正常或异常)
<T> List<Future<T>> invokeAll(
       Collection<? extends Callable<T>> tasks)
   throws InterruptedException

Future

image.png

Future方法

  • 试图取消任务的执行。如果参数为 true,则线程被中断。否则,允许完成任务。
boolean cancel(boolean mayInterruptIfRunning)
  • 等待任务完成(无限期) ,然后检索其结果。
V get() throws InterruptedException, ExecutionException
  • 最多等待给定的时间,然后检索结果(如果达到时间而结果没有准备好,则抛出 TimeoutException)。
V get(long timeout, TimeUnit unit)
    throws InterruptedException,
           ExecutionException,
           TimeoutException
  • 如果此任务在正常完成之前被取消,则返回 true。
boolean isCancelled()
  • 如果任务已完成,返回 true
boolean isDone()

Callable接口

也是一个函数式接口,只有一个抽象方法。
与Runnable 的区别在于 Callable 可以返回一个值并抛出一个Exception
image.png

V call() throws Exception;
  • Runnable不返回结果
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable r = () -> {
    IntStream.range(1, 1_000_000).forEach(System.out::println);
};
Future<?> future = executor.submit(r);
try {
    // Blocks until the Runnable has finished
    future.get();
} catch (InterruptedException
    | ExecutionException e) { /** ... */ }
  • Callable返回结果
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Long> c = () ->
    LongStream.rangeClosed(1, 1_000_000).sum();
Future<Long> future = executor.submit(c);
try {
    // Blocks 1 second until the Callable has finished
    Long result = future.get(1, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException
    | TimeoutException e) { /** ... */ }
  • 示例
List<Callable<String>> callables = new ArrayList<>();
        callables.add(() -> "Callable 1");
        callables.add(() -> "Callable 2");
        callables.add(() -> "Callable 3");
        ExecutorService executor = Executors.newFixedThreadPool(3);
        try {
            String result = executor.invokeAny(callables);
            System.out.println(result);
        } catch(InterruptedException | ExecutionException e) {/** ... */}

invokeAny ()执行给定的任务,返回成功完成任务的结果。你不能保证你会得到哪些 Callable 的结果,只能得到其中一个结果:
image.png
或者
image.png
invokeAll ()执行给定的任务,返回一个 Future 对象列表,该列表将保存状态和结果,直到所有任务完成。Isdone ()对返回的列表中的每个元素返回 true:

ExecutorService executor = Executors.newFixedThreadPool(3);
try {
    List<Future<String>> futures = executor.invokeAll(callables);
    for(Future<String> f : futures){
        System.out.format("%s - %s%n", f.get(), f.isDone());
    }
} catch(InterruptedException | ExecutionException e) { /** ... */ }

线程问题

  • 问题
    一个应用程序有两个或多个线程,线程并行执行的方式使得它们有时不得不竞争访问资源,而在其他时候,一个线程的操作会对另一个线程的操作产生副作用(比如修改或删除一些共享值)
  • 锁的解决方案
    一个解决方案是锁定的概念,其中资源或代码块之类的东西被某种机制锁定,一次只有一个线程可以使用或访问它。

锁的问题

使用不当锁资源可能会导致以下三个问题

死锁

两个或多个线程永远被阻塞,等待彼此获取/释放某些资源。

饥饿锁

一个线程不断地等待一个锁,而且永远不能使用它,因为其他拥有更高优先级的线程不断地获取它。
VIP客户插队

活锁

两个(或更多)线程相互阻塞,但在活锁中,每个线程都试图自己解决问题,而不是等待(死)。他们没有受到阻碍,但他们无法取得进一步的进展。
互相让资源

锁竞争

两个线程同时竞争以导致意外结果(通常是无效数据)的方式访问或修改同一资源的情况。

解决

1.不修改变量(为final),例如lambda和内部类因为堆栈存储可见性问题,导致局部变量使用时必须定义为fianl防止改变
2.原子地执行读写操作(在单个步骤中一起执行)。
3.是确保发生问题的部分一次只由一个线程正确执行

总结

  • 在底层通过两种方式创建线程,一种是实现Runnable,另一种是继承thread并重写run()方法。
  • 框架层面使用Executors,执行器使用线程池,线程池又使用工作线程。
  • 一种类型的线程池是固定线程池,它有固定数量的线程在运行。也可以使用单线程池。
  • ExecutorService具有执行线程池的方法,这些线程池接受可运行或可调用的任务。Callable返回一个结果并抛出一个Exception。
  • submit()方法返回表示任务结果的Future对象(如果任务是可运行的,则返回null)。
  • 使用shutdown()(优雅地)或shutdownNow()(强制地)关闭池线程
  • 当两个或多个线程被永远阻塞,等待彼此获取/释放某些资源时,就会出现死锁情况。
  • 当一个线程一直在等待一个锁,却因为其他优先级更高的线程一直在获取它而永远无法获取它时,就会发生饥饿锁。
  • 在两个(或更多)线程相互阻塞的意义上,活锁类似于死锁,但是在活锁中中,每个线程都试图自己(活动)解决问题,而不是仅仅等待(死)。
  • 争用条件是指两个线程同时竞争访问或修改同一资源,从而导致意外结果的情况。

这个家伙很懒,啥也没有留下😋