线程
开启线程的方式
Java开启线程有三种方式,每一种都有不同的特点。
1.实现 Runnable 接口,重写run()方法。
2.实现 Callable 接口,重写call()方法,这种方式是有返回值的。
3.继承 Thread 类,重写run()方法。
接下来请看这三种方式的代码demo
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyRunnable myRunnable = new MyRunnable();
System.out.println("程序开始");
// 使用 Runnable 的两种方式
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(() ->{
System.out.println("我是 lambda Runnable");
});
t1.start();
t2.start();
// 使用 Callable 的两种方式,可获取返回结果的线程
MyCallable mc = new MyCallable();
FutureTask<String> ft1 = new FutureTask<>(mc);
Thread t3 = new Thread(ft1);
FutureTask<String> ft2 = new FutureTask<>(() -> {
System.out.println("进入 lambda Callable");
Thread.sleep(3000);
return "我是 lambda Callable";
});
Thread t4 = new Thread(ft2);
t3.start();
t4.start();
System.out.println("同步获取 ft1: " + ft1.get());
System.out.println("同步获取 ft2: " + ft2.get());
// MyThread
MyThread t5 = new MyThread();
t5.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是 MyRunnable");
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("进入MyCallable");
Thread.sleep(3000);
return "我是 MyCallable";
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("我是 MyThread");
}
}
深入了解内部结构
start方法做了什么
从代码中我们可以看出,无论是哪种方法,最后都用通过Thread类的start方法来开启线程,让我们来看看start()方法来做了什么
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the {@code run} method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* {@code start} method) and the other thread (which executes its
* {@code run} method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @throws IllegalThreadStateException if the thread was already started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
start()方法被synchronized修饰,说明只能同步执行,并且start()方法调用了被native修饰的start0()方法,说明这是一个jvm内部执行的一个本地方法。并且官方在注释中说明,调用start方法后,jvm将调用这个线程的run()方法(重点!!!,最终都是调用run)(the Java Virtual Machine calls the {@code run} method of this thread.)
各种继承关系
Thread 实现了 Runnable 接口
FutureTask 实现了RunnableFuture接口,而RunnableFuture继承了Runnable接口
由这些对象关系图也能看出,线程要做什么都是要通过Runnable的run方法。
线程池
说到线程池不得不提Java提供的Executor框架,它能帮开发者便捷的使用线程。
Executor 框架结构
- 任务(
Runnable/Callable)
执行任务需要实现Runnable(重写run方法,无返回值)或Callable(重写call方法,并且有返回值),Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行 - 任务的执行(
Executor)
如下图所示,核心接口为Executor和ExecutorService以及两个关键实现类ThreadPoolExecutor和ScheduledThreadPoolExecutor,在实际开发中我们很少使用ScheduledThreadPoolExecutor,所以重点关注ThreadPoolExecutor就好了。 - 异步计算的结果(
Future)
Future接口以及Future接口的实现类FutureTask类都可以代表异步计算的结果。
当我们把Runnable接口或Callable接口的实现类提交给ThreadPoolExecutor或ScheduledThreadPoolExecutor执行,即调用submit()方法时会返回一个FutureTask对象。
Executor 框架的使用示意图
- 通过实现
Runnable或Callable接口,创建任务对象。 - 把实现了
Runnable/Callable接口的对象直接交给ExecutorService执行:ExecutorService.execute(Runnable command)/ExecutorService.submit(Runnable task)/ExecutorService.submit(Callable <T> task) - 如果执行
ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象(我们刚刚也提到过了执行execute()方法和submit()方法的区别,submit()会返回一个FutureTask对象)。由于FutureTask实现了Runnable,我们也可以创建FutureTask,然后直接交给ExecutorService执行。 - 最后,主线程可以执行
FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
ThreadPoolExecutor 类简单介绍
ThreadPoolExecutor类是Executor框架中最核心的类。Alibaba Java 开发手册中强制要求以构造函数的形式创建ThreadPoolExecutor对象,但Java也提供了Executors工具类来快捷的创建ThreadPoolExecutor对象。接下来我们先分析一下ThreadPoolExecutor类的核心构造函数。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这个类非常重要,必须牢记!
ThreadPoolExecutor 3 个最重要的参数
corePoolSize: 核心线程数线程数定义了最小可以同时运行的线程数量。maximumPoolSize: 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
ThreadPoolExecutor 代码 Demo
- 调用
ExecutorService.execute(Runnable command)
import java.time.LocalDateTime;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RunnableExecutorDemo {
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
//使用阿里巴巴推荐的创建线程池的方式
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
//执行Runnable
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " Start. Time = " + LocalDateTime.now());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " End. Time = " + LocalDateTime.now());
});
}
//终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
- 调用
ExecutorService.submit(Callable <T> task)
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExecutorDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 实际项目中用构造函数初始化
ExecutorService executorService = Executors.newFixedThreadPool(3);
System.out.println("time " + LocalDateTime.now());
List<Future<String>> futureList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int finalI = i;
Future<String> submit = executorService.submit(() -> {
return Thread.currentThread().getName() + " " + finalI;
});
System.out.println(submit.getClass().getName());
futureList.add(submit);
}
for (Future<String> stringFuture : futureList) {
String s = stringFuture.get();
System.out.println(s);
}
System.out.println("time " + LocalDateTime.now());
executorService.shutdown();
}
}
CompletableFuture:组合式异步编程
Future优缺点
Future优点--可以方便的以异步的方式执行一些简单长耗时任务。
ExecutorService executor = Executors.newCachedThreadPool(); ←─创建Executor-Service,通过它你可以向线程池提交任务
Future<Double> future = executor.submit(() -> { ←─向Executor-Service提交一个Callable对象
return doSomeLongComputation(); ←─以异步方式在新的线程中执行耗时的操作
});
doSomethingElse(); ←─异步操作进行的同时,你可以做其他的事情
try {
Double result = future.get(1, TimeUnit.SECONDS); ←─获取异步操作的结果,如果最终被阻塞,无法得到结果,那么在最多等待1秒钟之后退出
} catch (ExecutionException ee) {
// 计算抛出一个异常
} catch (InterruptedException ie) {
// 当前线程在等待过程中被中断
} catch (TimeoutException te) {
// 在Future对象完成之前超过已过期
}
Future的局限性- 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
- 等待Future集合中的所有任务都完成。
- 仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
- 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
- 应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)。
- 等等
商店最优价格demo
以下将通过多线程的方式在多个商店中获取某个商品的价格来展示如何使用CompletableFuture
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Shop {
private final Random random = new Random();
private String name;
public Shop(String name) {
this.name = name;
}
/***
* 模拟延迟
*/
public static void delay() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private double calculatePrice(String product) {
delay();
// 人为添加一个异常
if (product.equals("test product")) {
throw new RuntimeException("测试产品");
}
return random.nextDouble() * product.charAt(0) + product.charAt(1);
}
public Future<Double> getPriceAsync(String product) {
// 实际项目中推荐使用自定义线程池
// 这种方式提供了错误管理机制,推荐使用这种方式创建Future对象
return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Discount {
public enum Code {
NONE(0), SILVER(5), GOLD(10), PLATINUM(15), DIAMOND(20);
private final int percentage;
Code(int percentage) {
this.percentage = percentage;
}
}
public static String applyDiscount(Quote quote) {
return quote.getShopName() + " price is " +
// 将折扣代码应用于商品最初的原始价格
Discount.apply(quote.getPrice(),
quote.getDiscountCode());
}
private static double apply(double price, Code code) {
// 模拟Discount服务响应的延迟
delay();
return price * (100 - code.percentage) / 100;
}
/***
* 模拟延迟
*/
public static void delay() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class Quote {
private final String shopName;
private final double price;
private final Discount.Code discountCode;
public Quote(String shopName, double price, Discount.Code code) {
this.shopName = shopName;
this.price = price;
this.discountCode = code;
}
public static Quote parse(String s) {
String[] split = s.split(":");
String shopName = split[0];
double price = Double.parseDouble(split[1]);
Discount.Code discountCode = Discount.Code.valueOf(split[2]);
return new Quote(shopName, price, discountCode);
}
public String getShopName() { return shopName; }
public double getPrice() { return price; }
public Discount.Code getDiscountCode() { return discountCode; }
}
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static java.util.stream.Collectors.toList;
public class Shops {
private static List<Shop> shops = Arrays.asList(new Shop("BestPrice"),
new Shop("LetsSaveBig"),
new Shop("MyFavoriteShop"),
new Shop("BuyItAll"),
new Shop("TestShop"),
new Shop("TestShop"),
new Shop("TestShop"),
new Shop("TestShop"),
new Shop("TestShop"),
new Shop("TestShop"));
public static void main(String[] args) {
long start = System.nanoTime();
System.out.println(findPrices3("myPhone27S"));
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");
}
/**
* 同步顺序获取价格
* 不建议使用这种方式
*
* @param product
* @return
*/
@Deprecated
public static List<String> findPrices1(String product) {
return shops.stream()
.map(shop -> String.format("%s price is %.2f",
shop.getName(), shop.getPrice(product)))
.collect(toList());
}
/**
* 使用并行流获取价格
* 如果是计算密集型且不涉及io,推荐使用并行流
*
* @param product
* @return
*/
public static List<String> findPrices2(String product) {
// 使用并行流并行地从不同的商店获取价格
return shops.parallelStream()
.map(shop -> String.format("%s price is %.2f",
shop.getName(), shop.getPrice(product)))
.toList();
}
/**
* 通过构建CompletableFuture List 来找价格
* 如果线程中涉及io,使用CompletableFuture,因为可以灵活创建Executor
*
* @param product
* @return
*/
/**
* 通过构建CompletableFuture List 来找价格
* 如果线程中涉及io,使用CompletableFuture,因为可以灵活创建Executor
* @param product
* @return
*/
public static List<String> findPrices3(String product) {
// 这里是实际项目中的写法,测试的话可以利用Executors生成
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy());
List<CompletableFuture<String>> priceFutures =
shops.stream()
.map(shop -> CompletableFuture.supplyAsync(
//使用CompletableFuture以异步方式计算每种商品的价格
() -> shop.getName() + " price is " +
shop.getPrice(product), threadPoolExecutor))
.toList();
List<String> prices = priceFutures.stream()
//等待所有异步操作结束
.map(CompletableFuture::join).toList();
threadPoolExecutor.shutdown();
return prices;
}
/**
* 同步流获取折扣后的价格,实际项目中不建议使用
*
* @param product
* @return
*/
public static List<String> findPrices4(String product) {
return shops.stream()
// 取得每个shop对象中商品的原始价格
.map(shop -> shop.getPrice(product))
// 在Quote 对象中对shop返回的字符串进行转换
.map(Quote::parse)
// 联系Discount服务,为每个Quote申请折扣
.map(Discount::applyDiscount)
.collect(toList());
}
/**
* 利用CompletableFuture做异步操作
*
* @param product
* @return
*/
public static List<String> findPrices5(String product) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy());
List<CompletableFuture<String>> priceFutures =
shops.stream()
// 以异步方式取得每个shop中指定产品的原始价格
.map(shop -> CompletableFuture.supplyAsync(
() -> shop.getPrice(product), executor))
// Quote对象存在时,对其返回的值进行转换
.map(future -> future.thenApply(Quote::parse))
// 使用另一个异步任务构造期望的Future,申请折扣
.map(future -> future.thenCompose(quote ->
CompletableFuture.supplyAsync(
() -> Discount.applyDiscount(quote), executor)))
.toList();
List<String> prices = priceFutures.stream()
// 等待流中的所有Future执行完毕,并提取各自的返回值
.map(CompletableFuture::join)
.collect(toList());
executor.shutdown();
return prices;
}
}
在spring中使用Executor
具体信息可以去spring官网看,以下是作者的代码总结。
下面将介绍一个异步的加法计算demo
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("testExecutor")
public Executor pgsqlExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:线程池创建时候初始化的线程数
executor.setCorePoolSize(5);
// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(10);
// 缓冲队列:用来缓冲执行任务的队列
executor.setQueueCapacity(100);
// 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("testExecutor-");
// 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
@Service
public class CalculateService {
private static final Random random = new Random();
/**
* 随机等待0.5s ~ 2.5s,模拟实际
*/
private static void randomDelay() {
int delay = 500 + random.nextInt(2000);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 等待1s,用这个测试的时候能明显的看出效果
*/
private static void delay() {
int delay = 1000;
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Async("testExecutor")
public CompletableFuture<Double> addAsync(Double n1, Double n2) {
delay();
return CompletableFuture.completedFuture(n1 + n2);
}
@Async("testExecutor")
public CompletableFuture<Double> addAsyncRandomTime(Double n1, Double n2) {
randomDelay();
return CompletableFuture.completedFuture(n1 + n2);
}
}
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@SpringBootTest
public class CalculateServiceTest {
@Autowired
private CalculateService calculateService;
@Test
public void addTest() throws ExecutionException, InterruptedException {
System.out.println("start: " + LocalDateTime.now());
CompletableFuture<Double> future = calculateService.addAsync(2.0, 2.0);
System.out.println("异步等待中...");
System.out.println("结果: " + future.get());
System.out.println("end: " + LocalDateTime.now());
}
/**
* 获取这些future计算结果的和
*/
@Test
public void addSumTest() {
System.out.println("start: " + LocalDateTime.now());
List<CompletableFuture<Double>> futureList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CompletableFuture<Double> future = calculateService.addAsync((double) i, (double) i);
futureList.add(future);
}
System.out.println("异步等待中...");
double sum = futureList.stream()
// 等待所有future计算完毕
.map(CompletableFuture::join)
// 利用统计流获取总和
.mapToDouble((x) -> x).summaryStatistics().getSum();
System.out.println("和为: " + sum);
System.out.println("end: " + LocalDateTime.now());
}
/**
* 在这些future中,获取最快的计算结果
*/
@Test
public void fastAddTest() throws ExecutionException, InterruptedException {
System.out.println("start: " + LocalDateTime.now());
List<CompletableFuture<Double>> futureList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CompletableFuture<Double> future = calculateService.addAsyncRandomTime((double) i, (double) i);
futureList.add(future);
}
System.out.println("异步等待中...");
// 获取最快的结果
CompletableFuture<Object> fastFuture = CompletableFuture.anyOf(futureList.toArray(new CompletableFuture[0]));
System.out.println("最快的计算结果为: " + fastFuture.get());
System.out.println("end: " + LocalDateTime.now());
}
}
引用
- 本文档引用了JavaGuide,这里只做了一些简单介绍,算是自己的学习总结,详细介绍请去JavaGuide
- 还参考了《Java8 实战》的
CompletableFuture部分