JUC学习笔记——进程与线程( 二 )

同步与异步实际使用我们通常采用异步操作来实现应用的速度提升:
// 例如我们有下面三个操作计算 1 花费 10 ms计算 2 花费 11 ms计算 3 花费 9 ms汇总需要 1 ms如果我们采用主线程的同步操作来实现:
// 如果是串行执行,那么总共花费的时间是 10 + 11 + 9 + 1 = 31ms但是如果我们采用三个CPU的异步操作来实现:
// 但如果是四核 cpu,各个核心分别使用线程 1 执行计算 1,线程 2 执行计算 2,线程 3 执行计算 3// 那么 3 个线程是并行的,花费时间只取决于最长的那个线程运行的时间,即 11ms 最后加上汇总时间只会花费 12ms下面我们给出同步异步的实际使用规则:

  • 多核多线程速度快于单核多线程(异步进行速度较快)
  • 单核多线程速度慢于单核单线程(线程切换也需要耗费时间)
但是单核多线程也并非是一无是处:
  • 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换
  • 不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活
此外多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的:
  • 有些任务,经过精心设计,将任务拆分 , 并行执行 , 当然可以提高程序的运行效率 。但不是所有计算任务都能拆分
  • 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
最后就是我们的IO操作部分:
  • IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】
  • 这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程
  • 所以才有后面的【非阻塞 IO】和【异步 IO】优化
线程详解这一小节我们将详细介绍线程的具体内容
创建和运行线程我们下面将介绍三种创建和运行线程的方法
直接使用 Thread我们可以直接使用Thread来创建和运行线程:
// 创建线程对象Thread t = new Thread() {public void run() {// 要执行的任务}};// 启动线程t.start();我们再给出一个实际例子:
// 构造方法的参数是给线程指定名字,推荐Thread t1 = new Thread("t1") {@Override// run 方法内实现了要执行的任务public void run() {log.debug("hello");}};t1.start();我们给出实际输出结果:
// 我们会注意到:前面标记了[t1]线程~19:19:00 [t1] c.ThreadStarter - hello使用 Runnable 配合 Thread这里我们将Thread里面的方法采用Runnable类型的方法来代替:
// 创建Runnable类型的方法Runnable runnable = new Runnable() {public void run(){// 要执行的任务}};// 创建线程对象Thread t = new Thread( runnable );// 启动线程t.start();我们给出一个实际例子:
// 创建任务对象Runnable task2 = new Runnable() {@Overridepublic void run() {log.debug("hello");}};// 参数1 是任务对象; 参数2 是线程名字,推荐Thread t2 = new Thread(task2, "t2");t2.start();其结果为:
// 结果正常9:19:00 [t2] c.ThreadStarter - hello除此之外,我们在JDK8之后,我们可以采用函数式接口Lambda来简化Runnable的书写:
// 创建任务对象Runnable task2 = () -> log.debug("hello");// 参数1 是任务对象; 参数2 是线程名字,推荐Thread t2 = new Thread(task2, "t2");t2.start();【JUC学习笔记——进程与线程】甚至我们都不用定义task,来直接采用Lambda方法书写Thread中的task:
// 参数1 是任务对象; 参数2 是线程名字,推荐Thread t2 = new Thread(() -> log.debug("hello"), "t2");t2.start();底层简单解释:
  • 至于Thread为什么能够直接调用Runnable
  • Thread在接收Runnable类型后,会将其赋值在this.target
  • 而Thread的run方法会先来判断是否存在target,如果存在就直接采用target方法
最后我们介绍一下使用Runnable的好处:
  • 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
  • 用 Runnable 更容易与线程池等高级API 配合
  • 用 Runnable 让任务类脱离了 Thread 继承体系 , 更灵活
使用FutureTask 配合 Thread(了解即可)FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况:
// 创建任务对象(Integer是返回对象)FutureTask<Integer> task3 = new FutureTask<>(() -> { log.debug("hello"); return 100;});// 参数1 是任务对象; 参数2 是线程名字,推荐new Thread(task3, "t3").start();// 主线程阻塞,同步等待 task 执行完毕的结果Integer result = task3.get();log.debug("结果是:{}", result);

推荐阅读