107
社区成员




1.准备
pom.xml 依赖如下:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.22</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> </dependencies>
logback.xml 配置如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern> </encoder> </appender> <logger name="c" level="debug" additivity="false"> <appender-ref ref="STDOUT"/> </logger> <root level="ERROR"> <appender-ref ref="STDOUT"/> </root> </configuration>
单核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
评价
Future 实现(同步)
private static void test2() throws InterruptedException, ExecutionException { log.debug("开始"); FutureTask<Integer> 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<Integer> 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
评价
自定义实现(同步)
见模式篇:保护性暂停模式
CompletableFuture 实现(异步)
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
评价
BlockingQueue 实现(异步)
private static void test6() { ExecutorService consumer = Executors.newFixedThreadPool(1); ExecutorService producer = Executors.newFixedThreadPool(1); BlockingQueue<Integer> 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
CompletableFuture 实现
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.设计
代码见【应用之效率-案例1】
2.结论