1.准备
pom.xml 依赖如下:
UTF-8 1.8 1.8 junit junit 4.11 test org.projectlombok lombok 1.18.22 provided org.slf4j slf4j-api 1.7.22 ch.qos.logback logback-classic 1.2.3 org.junit.jupiter junit-jupiter RELEASE compile
logback.xml 配置如下:
%date{HH:mm:ss} [%t] %logger - %m%n
单核cpu下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。总结为一句话就是: 微观串行,宏观并行 。
一般会将这种线程轮流使用 CPU 的做法称为并发, concurrent
CPU | 时间片 1 | 时间片 2 | 时间片 3 | 时间片 4 |
---|---|---|---|---|
core | 线程 1 | 线程 2 | 线程 3 | 线程 4 |
多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。
CPU | 时间片 1 | 时间片 2 | 时间片 3 | 时间片 4 |
---|---|---|---|---|
core1 | 线程 1 | 线程 2 | 线程 3 | 线程 4 |
core2 | 线程 4 | 线程 4 | 线程 2 | 线程 2 |
引用 Rob Pike 的一段描述:
并发(concurrent)是同一时间应对(dealing with)多件事情的能力 。
并行(parallel)是同一时间动手做(doing)多件事情的能力。
这时既可以使用同步处理,也可以使用异步来处理
join 实现(同步)
static int result = 0;
private static void test1() throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");sleep(1);log.debug("结束");result = 10;}, "t1");t1.start();t1.join();log.debug("结果为:{}", result);
}
输出
20:30:40.453 [main] c.TestJoin - 开始
20:30:40.541 [Thread-0] c.TestJoin - 开始
20:30:41.543 [Thread-0] c.TestJoin - 结束
20:30:41.551 [main] c.TestJoin - 结果为:10
评价
private static void test2() throws InterruptedException, ExecutionException {log.debug("开始");FutureTask result = new FutureTask<>(() -> {log.debug("开始");sleep(1);log.debug("结束");return 10;});new Thread(result, "t1").start();log.debug("结果为:{}", result.get());
}
输出
10:11:57.880 c.TestSync [main] - 开始
10:11:57.942 c.TestSync [t1] - 开始
10:11:58.943 c.TestSync [t1] - 结束
10:11:58.943 c.TestSync [main] - 结果为:10
评价
private static void test3() throws InterruptedException, ExecutionException {ExecutorService service = Executors.newFixedThreadPool(1);log.debug("开始");Future result = service.submit(() -> {log.debug("开始");sleep(1);log.debug("结束");return 10;});log.debug("结果为:{}, result 的类型:{}", result.get(), result.getClass());service.shutdown();
}
输出
10:17:40.090 c.TestSync [main] - 开始
10:17:40.150 c.TestSync [pool-1-thread-1] - 开始
10:17:41.151 c.TestSync [pool-1-thread-1] - 结束
10:17:41.151 c.TestSync [main] - 结果为:10, result 的类型:class java.util.concurrent.FutureTask
评价
见模式篇:保护性暂停模式
private static void test4() {// 进行计算的线程池ExecutorService computeService = Executors.newFixedThreadPool(1);// 接收结果的线程池ExecutorService resultService = Executors.newFixedThreadPool(1);log.debug("开始");CompletableFuture.supplyAsync(() -> {log.debug("开始");sleep(1);log.debug("结束");return 10;}, computeService).thenAcceptAsync((result) -> {log.debug("结果为:{}", result);}, resultService);
}
输出
10:36:28.114 c.TestSync [main] - 开始
10:36:28.164 c.TestSync [pool-1-thread-1] - 开始
10:36:29.165 c.TestSync [pool-1-thread-1] - 结束
10:36:29.165 c.TestSync [pool-2-thread-1] - 结果为:10
评价
private static void test6() {ExecutorService consumer = Executors.newFixedThreadPool(1);ExecutorService producer = Executors.newFixedThreadPool(1);BlockingQueue queue = new SynchronousQueue<>();log.debug("开始");producer.submit(() -> {log.debug("开始");sleep(1);log.debug("结束");try {queue.put(10);} catch (InterruptedException e) {e.printStackTrace();}});consumer.submit(() -> {try {Integer result = queue.take();log.debug("结果为:{}", result);} catch (InterruptedException e) {e.printStackTrace();}});
}
这时最好是使用异步来处理
@Slf4j(topic = "c.FileReader")
public class FileReader {public static void read(String filename) {int idx = filename.lastIndexOf(File.separator);String shortName = filename.substring(idx + 1);try (FileInputStream in = new FileInputStream(filename)) {long start = System.currentTimeMillis();log.debug("read [{}] start ...", shortName);byte[] buf = new byte[1024];int n = -1;do {n = in.read(buf);} while (n != -1);long end = System.currentTimeMillis();log.debug("read [{}] end ... cost: {} ms", shortName, end - start);} catch (IOException e) {e.printStackTrace();}}
}
没有用线程时,方法的调用是同步的:
@Slf4j(topic = "c.Sync")
public class Sync {public static void main(String[] args) {String fullPath = "E:\1.mp4";FileReader.read(fullPath);log.debug("do other things ...");}
}
输出
18:39:15 [main] c.FileReader - read [1.mp4] start ...
18:39:19 [main] c.FileReader - read [1.mp4] end ... cost: 4090 ms
18:39:19 [main] c.Sync - do other things ...
使用了线程后,方法的调用时异步的:
private static void test1() {new Thread(() -> FileReader.read(Constants.MP4_FULL_PATH)).start();log.debug("do other things ...");
}
输出
18:41:53 [main] c.Async - do other things ...
18:41:53 [Thread-0] c.FileReader - read [1.mp4] start ...
18:41:57 [Thread-0] c.FileReader - read [1.mp4] end ... cost: 4197 ms
private static void test2() {ExecutorService service = Executors.newFixedThreadPool(1);service.execute(() -> FileReader.read(Constants.MP4_FULL_PATH));log.debug("do other things ...");service.shutdown();
}
输出
11:03:31.245 c.TestAsyc [main] - do other things ...
11:03:31.245 c.FileReader [pool-1-thread-1] - read [1.mp4] start ...
11:03:33.479 c.FileReader [pool-1-thread-1] - read [1.mp4] end ... cost: 2235 ms
private static void test3() throws IOException {CompletableFuture.runAsync(() -> FileReader.read(Constants.MP4_FULL_PATH));log.debug("do other things ...");System.in.read();
}
输出
11:09:38.145 c.TestAsyc [main] - do other things ...
11:09:38.145 c.FileReader [ForkJoinPool.commonPool-worker-1] - read [1.mp4] start ...
11:09:40.514 c.FileReader [ForkJoinPool.commonPool-worker-1] - read [1.mp4] end ... cost: 2369 ms
以调用方角度来讲,
1.设计
多线程可以让方法执行变为异步的(即不要巴巴干等着)、比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这 5 秒 cpu 什么都做不了,其它代码都得暂停…
2.结论
充分利用多核 cpu 的优势,提高运行效率。想象下面的场景,执行 3 个计算,最后将计算结果汇总。
计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
注意:
需要在多核 cpu 才能提高效率,单核仍然时是轮流执行
代码见【应用之效率-案例1】