并发 编程

1. 进程与线程

1.1 进程与线程

【1】进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

【2】线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows 中进程是不活动的,只是作为线程的容器

【3】二者对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

1.2 并行与并发

        单核 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线程1线程2线程3线程4

在这里插入图片描述
引用 Rob Pike 的一段描述:

  • 并发(concurrent) 是同一时间应对(dealing with)多件事情的能力
  • 并行(parallel) 是同一时间动手做(doing)多件事情的能力

例子

  • 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发
  • 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)
  • 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行

1.3 应用

【1】应用之异步调用

以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

1) 设计
        多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这 5 秒 cpu 什么都做不了,其它代码都得暂停…

2) 结论
        比如在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程tomcat 的异步 servlet 也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程ui 程序中,开线程进行其他操作,避免阻塞 ui 线程

【2】应用之提高效率

        充分利用多核 cpu 的优势,提高运行效率。想象下面的场景,执行 3 个计算,最后将计算结果汇总。

计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
  • 如果是串行执行,那么总共花费的时间是 10 + 11 + 9 + 1 = 31ms
  • 但如果是四核 cpu,各个核心分别使用线程 1 执行计算 1,线程 2 执行计算 2,线程 3 执行计算 3,那么 3 个线程是并行的,花费时间只取决于最长的那个线程运行的时间,即 11ms 最后加上汇总时间只会花费 12ms

注意 : 需要在多核 cpu 才能提高效率,单核仍然时是轮流执行

结论

  • 1.单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活
  • 2.多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的
    • 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任务都能拆分(参考后文的【阿姆达尔定律】)
    • 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
  • 3.IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化

2. Java线程

2.1 创建和运行线程

【1】直接使用Thread

@Slf4j
public class ThreadTest {
    public static void main(String[] args) {
        // 创建线程对象,构造方法参数(t1)是给线程指定名字
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                // run方法内实现了要执行的任务
                log.info("线程执行=========");
            }
        };
        t.start();
        log.info("main方法执行============");
    }
}

输出:

16:57:28.678 [main] INFO com.wyc.test.ThreadTest - main方法执行============
16:57:28.678 [t1] INFO com.wyc.test.ThreadTest - 线程执行=========

【2】使用 Runnable 配合 Thread

把【线程】和【任务】(要执行的代码)分开

  • Thread 代表线程
  • Runnable 可运行的任务(线程要执行的代码)
@Slf4j
public class ThreadAndRunnable {
    public static void main(String[] args) {
        // 创建任务对象
        Runnable r = new Runnable() {
            @Override
            public void run() {
                // 要执行的任务
                log.info("Runnable执行=======");
            }
        };
        // 创建线程对象, 参数1是任务对象, 参数2是线程名字
        Thread t = new Thread(r,"t2");
        // 启动线程
        t.start();
    }
}

Java 8 以后可以使用 lambda 精简代码

        // Java 8 以后可以使用 lambda 精简代码
        Runnable r = () -> {
            // 要执行的任务
            log.info("Runnable执行=======");
        };

Thread 与 Runnable 的关系

  • Thread 是把线程和任务合并在了一起,Runnable 是把线程和任务分开了
  • 用 Runnable 更容易与线程池等高级 API 配合
  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

【3】FutureTask 配合 Thread

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

@Slf4j
public class ThreadAndFutureTask {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.info("FutureTask执行=======");
                Thread.sleep(1000);
                return 100;
            }
        });

        Thread t = new Thread(task,"t1");
        t.start();

        Integer result = task.get();
        log.info("获取到返回结果:{}", result);
    }
}

输出结果:

17:51:53.245 [t1] INFO com.wyc.test.ThreadAndFutureTask - FutureTask执行=======
17:51:54.249 [main] INFO com.wyc.test.ThreadAndFutureTask - 获取到返回结果:100

2.2 查看进程线程的方法

【1】windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程
  • taskkill 杀死进程

【2】 linux

  • ps -ef 查看所有进程
  • ps -fT -p 查看某个进程(PID)的所有线程
  • kill 杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p 查看某个进程(PID)的所有线程

【3】Java

  • jps 命令查看所有 Java 进程
  • jstack 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

2.3 原理之线程运行

【1】栈与栈帧

        Java Virtual Machine Stacks (Java 虚拟机栈)
        我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟
机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
public class TestFrames {
    public static void main(String[] args) {
        method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object n = new Object();
        return n;
    }
}

在这里插入图片描述

【2】线程上下文切换(Thread Context Switch)

        因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

        当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

2.4 常见方法

方法名static功能说明注意
start()启动一个新线程,在新的线程运行 run 方法中的代码start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException
run()新线程启动后会调用的方法如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
join()等待线程运行结束
join(long n)等待线程运行结束,最多等待 n 毫秒
getId()获取线程长整型的 idid 唯一
getName()获取线程名
setName(String)修改线程名
getPriority()获取线程优先级
setPriority(int)修改线程优先级java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率
getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted()判断是否被打断不会清除 打断标记
isAlive()线程是否存活(还没有运行完毕)
interrupt()打断线程如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除 打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记
interrupted()static判断当前线程是否被打断会清除 打断标记
currentThread()staticc 获取当前正在执行的线程
sleep(long n)static让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程
yield()static提示线程调度器让出当前线程对CPU的使用主要是为了测试和调试

2.5 start 与 run

调用 run

@Slf4j
public class StartAndRun {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                FileReader.read();
               log.info("线程执行===");
            }
        };
        t1.run();
        log.info("其他事情===");
    }
}

输出

22:08:51.815 [main] DEBUG com.wyc.n2.util.FileReader - 做了其他事共花费: 2001 ms
22:08:51.829 [main] INFO com.wyc.test.StartAndRun - 线程执行===
22:08:51.829 [main] INFO com.wyc.test.StartAndRun - 其他事情===

程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的

调用 start
将上述代码的 t1.run() 改为 t1.start();
输出

22:10:22.051 [main] INFO com.wyc.test.StartAndRun - 其他事情===
22:10:24.051 [t1] DEBUG com.wyc.n2.util.FileReader - 做了其他事共花费: 2001 ms
22:10:24.078 [t1] INFO com.wyc.test.StartAndRun - 线程执行===

程序在 t1 线程运行, FileReader.read() 方法调用是异步的

小节

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

2.6 sleep 与 yield

【1】sleep

  • 1.调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • 2.其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 3.睡眠结束后的线程未必会立刻得到执行
  • 4.建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
@Slf4j
public class SleepTest {
    public static void main(String[] args) throws InterruptedException {
        log.info("睡眠前===");
        Thread.sleep(1000);
        // 另一种睡眠方式写法
        TimeUnit.SECONDS.sleep(1);
        log.info("睡眠后===");
    }
}

【2】yield

  • 1.调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  • 2.具体的实现依赖于操作系统的任务调度器

【3】线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
        // 设置线程优先级
        thread.setPriority(Thread.MAX_PRIORITY);

【4】限制对 CPU 的使用

sleep 实现
        在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序

        while(true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
  • 可以用 wait 或 条件变量达到类似的效果
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep 适用于无需锁同步的场景

2.7 join 方法详解

下面的代码执行,打印 r 是什么?

@Slf4j
public class JoinTest {
    static int r = 0;

    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        log.info("方法开始===");
        Thread t1 = new Thread("t1"){
            @SneakyThrows
            @Override
            public void run() {
                log.info("线程开始==");
                sleep(1);
                log.info("线程结束==");
                r = 10;
            }
        };

        t1.start();
        log.info("结果为:{}",r);
        log.info("方法结束===");
    }
}

分析

  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
  • 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0

解决方法

  • 用 sleep 行不行?为什么?
  • 用 join,加在 t1.start() 之后即可
t1.join();

2.8 interrupt 方法详解

【1】打断 sleep,wait,join 的线程

这几个方法都会让线程进入阻塞状态
打断 sleep 的线程, 会清空打断状态,以 sleep 为例

@Slf4j
public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.info("睡眠==");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.info("打断==");
        t1.interrupt();
        log.info("打断标记:{}",t1.isInterrupted());
    }
}

输出

17:58:08.409 [t1] INFO com.wyc.test.InterruptTest - 睡眠==
17:58:09.407 [main] INFO com.wyc.test.InterruptTest - 打断==
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.wyc.test.InterruptTest$1.run(InterruptTest.java:17)
17:58:09.407 [main] INFO com.wyc.test.InterruptTest - 打断标记:true

【2】打断正常运行的线程

@Slf4j
public class InterruptTest1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                while(true){
                    boolean interrupted = Thread.currentThread().isInterrupted();
                    if (interrupted){
                        log.info("被打断了,退出循环");
                        break;
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.info("打断==");
        t1.interrupt();
        log.info("打断标记:{}",t1.isInterrupted());
    }
}

输出:

20:13:30.680 [main] INFO com.wyc.test.InterruptTest1 - 打断==
20:13:30.682 [t1] INFO com.wyc.test.InterruptTest1 - 被打断了,退出循环
20:13:30.682 [main] INFO com.wyc.test.InterruptTest1 - 打断标记:true

2.9 主线程与守护线程

        默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

@Slf4j
public class Test00 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while (true){
                if (Thread.currentThread().isInterrupted()){
                    log.info("线程退出==");
                    break;
                }
            }
        },"t1");
        // 设置该线程为守护线程
        t1.setDaemon(true);
        t1.start();
        Thread.sleep(1000);
        log.info("main方法结束");
    }
}

注意

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

2.10 五种状态

这是从操作系统层面来描述的
在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

2.11 六种状态

这是从 Java API 层面来描述的
根据 Thread.State 枚举,分为六种状态
在这里插入图片描述

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
  • TERMINATED 当线程代码运行结束
    代码例子:
@Slf4j
public class TestState {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
    }
}

输出:

21:50:52.644 [t3] DEBUG com.wyc.n3.TestState - running...
21:50:53.144 [main] DEBUG com.wyc.n3.TestState - t1 state NEW
21:50:53.156 [main] DEBUG com.wyc.n3.TestState - t2 state RUNNABLE
21:50:53.156 [main] DEBUG com.wyc.n3.TestState - t3 state TERMINATED
21:50:53.156 [main] DEBUG com.wyc.n3.TestState - t4 state TIMED_WAITING
21:50:53.156 [main] DEBUG com.wyc.n3.TestState - t5 state WAITING
21:50:53.156 [main] DEBUG com.wyc.n3.TestState - t6 state BLOCKED

【1】情况1 NEW --> RUNNABLE

  • 当调用 t.start() 方法时,由 NEW --> RUNNABLE

【2】情况2 RUNNABLE <–> WAITING

t 线程用 synchronized(obj) 获取了对象锁后

  • 调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING
  • 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
    • 竞争锁成功,t 线程从 WAITING --> RUNNABLE
    • 竞争锁失败,t 线程从 WAITING --> BLOCKED

【3】情况3 RUNNABLE <–> WAITING

  • 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING
    • 注意是当前线程在t 线程对象的监视器上等待
  • t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE

【4】情况4 RUNNABLE <–> WAITING

  • 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING
  • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE

【5】情况5 RUNNABLE <–> TIMED_WAITING

t 线程用 synchronized(obj) 获取了对象锁后

  • 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING
  • t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
    • 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE
    • 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED

【6】情况6 RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING
    • 注意是当前线程在t 线程对象的监视器上等待
  • 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从TIMED_WAITING --> RUNNABLE

【7】情况7 RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
  • 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE

【8】情况8 RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE --> TIMED_WAITING
  • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从TIMED_WAITING–> RUNNABLE

【9】情况9 RUNNABLE <–> BLOCKED

  • t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED
  • 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED

【10】情况10 RUNNABLE <–> TERMINATED

  • 当前线程所有代码运行完毕,进入 TERMINATED

3. 共享模型之管程

3.1 synchronized

        虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

synchronized语法

    synchronized(对象){
        临界区
    }
@Slf4j
public class Test01 {
    static int count = 0;
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                synchronized (lock){
                    count ++;
                }
            }
        }, "t1");

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                synchronized (lock){
                    count --;
                }
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("{}",count);
    }
}

你可以做这样的类比:

  • synchronized(对象) 中的对象,可以想象为一个房间(room),有唯一入口(门)房间只能一次进入一人进行计算,线程 t1,t2 想象成两个人
  • 当线程 t1 执行到 synchronized(room) 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行count++ 代码
  • 这时候如果 t2 也运行到了 synchronized(room) 时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了
  • 这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去哦),
  • 这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才能开门进入
  • 当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 t2 线程把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count-- 代码

面向对象改进
把需要保护的共享变量放入一个类

@Slf4j
public class Test01 {

    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                room.increment();
            }
        }, "t1");

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                room.decrement();
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("{}",room.getCount());
    }
}

class Room {
    int count = 0;
    public void increment() {
        synchronized (this) {
            count++;
        }
    }
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }
    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}

3.2 方法上的 synchronized

class Test{
   public synchronized void test() {
 
   }
}
等价于
class Test{
   public void test() {
     synchronized(this) {
 
     }
   }
}
class Test{
     public synchronized static void test() {
     }
}
等价于
class Test{
     public static void test() {
         synchronized(Test.class) {
 
         }
     }
}

【1】线程八锁

情况1:先1后2 或 先2后1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n1.b();}).start();
    }
}
@Slf4j
class Number{
    public synchronized void a(){
        log.info("1");
    }
    public synchronized void b(){
        log.info("2");
    }
}

情况2:1s后12,或 2 1s后 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n1.b();}).start();
    }
}
@Slf4j
class Number{
    public synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public synchronized void b(){
        log.info("2");
    }
}

情况3:3 1s 12 或 23 1s 1 或 32 1s 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n1.b();}).start();
        new Thread(()->{ n1.c();}).start();
    }
}
@Slf4j
class Number{
    public synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public synchronized void b(){
        log.info("2");
    }

    public void c(){
        log.info("3");
    }
}

情况4:2 1s 后 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n2.b();}).start();
    }
}
@Slf4j
class Number{
    public synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public synchronized void b(){
        log.info("2");
    }
}

情况5:2 1s 后 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n1.b();}).start();
    }
}
@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public synchronized void b(){
        log.info("2");
    }
}

情况6:1s 后12, 或 2 1s后 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n1.b();}).start();
    }
}
@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public static synchronized void b(){
        log.info("2");
    }
}

情况7:2 1s 后 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n2.b();}).start();
    }
}
@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public synchronized void b(){
        log.info("2");
    }
}

情况8:1s 后12, 或 2 1s后 1

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(()->{ n1.a();}).start();
        new Thread(()->{ n2.b();}).start();
    }
}
@Slf4j
class Number{
    public static synchronized void a(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("1");
    }
    public static synchronized void b(){
        log.info("2");
    }
}

3.3 变量的线程安全分析

【1】成员变量和静态变量是否线程安全?

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
    • 如果只有读操作,则线程安全
    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

【2】局部变量是否线程安全?

  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果该对象逃离方法的作用范围,需要考虑线程安全

【3】局部变量线程安全分析

【3.1】成员变量
@Slf4j
public class TestThreadSafe {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}

class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();
    public final void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

        执行之后有两种情况,一种是正常执行,另一种是,如果线程2 还未 add,线程1 remove 就会报错:

Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:653)
	at java.util.ArrayList.remove(ArrayList.java:492)
	at com.wyc.test.ThreadSafe.method3(TestThreadSafe.java:39)
	at com.wyc.test.ThreadSafe.method1(TestThreadSafe.java:30)
	at com.wyc.test.TestThreadSafe.lambda$main$0(TestThreadSafe.java:19)
	at java.lang.Thread.run(Thread.java:745)

分析:

  • 无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量
  • method3 与 method2 分析相同
    在这里插入图片描述
【3.2】将 list 修改为局部变量
class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

分析:

  • list 是局部变量,每个线程调用时会创建其不同实例,没有共享
  • 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
  • method3 的参数分析与 method2 相同
    在这里插入图片描述

【4】常见线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

3.4 wait notify

【1】API介绍

  • obj.wait() 让进入 object 监视器的线程到 waitSet 等待
  • obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

        它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法

@Slf4j
public class Test02 {
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (lock){
                log.debug("执行.......");
                try {
                    // 让线程在obj上一直等待下去
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码......");
            }
        }, "t1").start();

        new Thread(()->{
            synchronized (lock){
                log.debug("执行.......");
                try {
                    // 让线程在obj上一直等待下去
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码......");
            }
        },"t2").start();

        //主线程两秒后执行
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("唤醒obj上其他线程");
        synchronized (lock){
//            lock.notify(); // 唤醒obj上一个线程
            lock.notifyAll(); //唤醒obj上所有等待线程
        }

    }
}

【2】sleep() 和 wait()的区别

  • sleep 是 Thread 方法,而 wait 是 Object 的方法
  • sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁 4) 它们状态 TIMED_WAITING
  • 它们状态都是TIMED_WAITING

3.5 ReentrantLock

相对于 synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {
     // 临界区
} finally {
     // 释放锁
     reentrantLock.unlock();
}

【1】可重入

        可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

@Slf4j
public class Test05 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("进入main");
            m1();
        } catch (Exception e) {
            lock.unlock();
        }
    }

    public static void m1(){
        lock.lock();
        try {
            log.debug("进入m1");
            m2();
        } catch (Exception e) {
            lock.unlock();
        }
    }

    public static void m2(){
        lock.lock();
        try {
            log.debug("进入m2");
        } catch (Exception e) {
            lock.unlock();
        }
    }
}

输出:

21:21:46.441 [main] DEBUG com.wyc.test.Test05 - 进入main
21:21:46.445 [main] DEBUG com.wyc.test.Test05 - 进入m1
21:21:46.445 [main] DEBUG com.wyc.test.Test05 - 进入m2

【2】可打断

@Slf4j
public class Test06 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                // 如果没有竞争那么此方法就会获取lock对象锁
                // 如果有竞争就进入阻塞队列,可以被其他线程用interrupt方法打断
                log.debug("尝试获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获取到锁,返回");
                return;
            }
            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("打断t1");
        t1.interrupt();
    }
}

输出

21:28:51.461 [t1] DEBUG com.wyc.test.Test06 - 尝试获取锁
21:28:52.457 [main] DEBUG com.wyc.test.Test06 - 打断t1
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.wyc.test.Test06.lambda$main$0(Test06.java:21)
	at java.lang.Thread.run(Thread.java:745)
21:28:52.458 [t1] DEBUG com.wyc.test.Test06 - 没有获取到锁,返回

【3】锁超时

立刻失败

@Slf4j
public class Test07 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获取锁");
            if (!lock.tryLock()){
                log.debug("获取不到锁");
                return;
            }
            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
    }
}

输出

21:33:16.078 [t1] DEBUG com.wyc.test.Test07 - 尝试获取锁
21:33:16.081 [t1] DEBUG com.wyc.test.Test07 - 获取不到锁

超时失败

@Slf4j
public class Test07 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获取锁");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)){
                    log.debug("获取等待 1s 后失败,返回");
                    log.debug("获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
    }
}

输出

21:35:20.894 [t1] DEBUG com.wyc.test.Test07 - 尝试获取锁
21:35:21.899 [t1] DEBUG com.wyc.test.Test07 - 获取等待 1s 后失败,返回
21:35:21.899 [t1] DEBUG com.wyc.test.Test07 - 获取不到锁

【4】公平锁

ReentrantLock 默认是不公平的

改为公平锁

ReentrantLock lock = new ReentrantLock(true);

公平锁一般没有必要,会降低并发度

【5】条件变量

        synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

        ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  • synchronized 是那些不满足条件的线程都在一间休息室等消息
  • 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j
public class Test08 {
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    private static ReentrantLock ROOM = new ReentrantLock();
    // 等待烟的休息室
    static Condition waitCigaretteSet = ROOM.newCondition();
    // 等待外卖的休息室
    static Condition waitTakeoutSet = ROOM.newCondition();
    
    public static void main(String[] args) {
        new Thread(()->{
            ROOM.lock();
            try {
                log.debug("有烟没?[{}]",hasCigarette);
                while (!hasCigarette){
                    log.debug("没烟,先歇会!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了....");
            } finally {
                ROOM.unlock();
            }
        },"小南").start();

        new Thread(()->{
            ROOM.lock();
            try {
                log.debug("外卖送到没?[{}]",hasTakeout);
                while (!hasTakeout){
                    log.debug("没外卖,先歇会!");
                    try {
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了....");
            } finally {
                ROOM.unlock();
            }
        },"小女").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            ROOM.lock();
            try {
                hasTakeout = true;
                waitTakeoutSet.signal();
            } finally {
                ROOM.unlock();
            }
        },"送外卖的").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            ROOM.lock();
            try {
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
                ROOM.unlock();
            }
        },"送烟的").start();


    }
}

输出

22:11:01.399 [小南] DEBUG com.wyc.test.Test08 - 有烟没?[false]
22:11:01.412 [小南] DEBUG com.wyc.test.Test08 - 没烟,先歇会!
22:11:01.412 [小女] DEBUG com.wyc.test.Test08 - 外卖送到没?[false]
22:11:01.412 [小女] DEBUG com.wyc.test.Test08 - 没外卖,先歇会!
22:11:02.399 [小女] DEBUG com.wyc.test.Test08 - 可以开始干活了....
22:11:03.399 [小南] DEBUG com.wyc.test.Test08 - 可以开始干活了....

3.6 交替输出

【1】 synchronized

@Slf4j
public class Test09 {
    public static void main(String[] args) {
        WaitNotify wn = new WaitNotify(1,5);
        new Thread(()->{
            wn.print("a", 1, 2);
        }).start();
        new Thread(()->{
            wn.print("b", 2, 3);
        }).start();
        new Thread(()->{
            wn.print("c", 3, 1);
        }).start();
    }
}

class WaitNotify{
    // 打印
    public void  print(String str, int waitFlag, int nextFlag){
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this){
                while (flag != waitFlag){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
    // 等待标记
    private int flag;
    // 循环次数
    private int loopNumber;

    public WaitNotify(int flag, int loopNumber){
        this.flag = flag;
        this.loopNumber = loopNumber;
    }
}

【2】ReentrantLock

@Slf4j
public class Test10 {
    public static void main(String[] args) {
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition a = awaitSignal.newCondition();
        Condition b = awaitSignal.newCondition();
        Condition c = awaitSignal.newCondition();
        new Thread(()->{
            awaitSignal.print("a",a, b);
        }).start();
        new Thread(()->{
            awaitSignal.print("b",b, c);
        }).start();
        new Thread(()->{
            awaitSignal.print("c",c, a);
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        awaitSignal.lock();
        try {
            log.debug("开始.....");
            a.signal();
        } finally {
            awaitSignal.unlock();
        }
    }
}

class AwaitSignal extends ReentrantLock{
    private int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    // 参数1:打印内容 参数2: 进入哪一间休息 参数3:下一件休息室
    public void print(String str, Condition current, Condition next){
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                current.await();
                System.out.println(str);
                next.signal();
            }catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                unlock();
            }
        }
    }
}

【3】Park

@Slf4j
public class Test11 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
        ParkUnpark pu = new ParkUnpark(5);
        t1 = new Thread(()->{
            pu.print("a",t2);
        });
        t2 = new Thread(()->{
            pu.print("b",t3);
        });
        t3 = new Thread(()->{
            pu.print("c",t1);
        });
        t1.start();
        t2.start();
        t3.start();
        LockSupport.unpark(t1);

    }
}

class ParkUnpark{
    public void print(String str, Thread next){
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.println(str);
            LockSupport.unpark(next);
        }
    }
    private int loopNumber;

    public ParkUnpark(int loopNumber){
        this.loopNumber = loopNumber;
    }
}

4. 共享模型之内存

4.1 Java 内存模型

        JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

4.2 可见性

【1】退不出的循环

@Slf4j
public class Test12 {

    static boolean run = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (run){

            }
        });
        t.start();
        Thread.sleep(1000);
        log.debug("停止t");
        run = false; // 线程t不会如预想的停下来
    }
}

为什么呢?分析一下:
        1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
在这里插入图片描述
        2. 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
在这里插入图片描述
        3. 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
在这里插入图片描述

【2】解决方法

        volatile(易变关键字)

volatile static boolean run = true;

        它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存

4.3 有序性

        JVM 会在不影响正确性的前提下,可以调整语句的执行顺序
        这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。

5. 线程池

5.1 自定义线程池

在这里插入图片描述

@Slf4j
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10,(queue, task)->{
            // 1. 死等
//            queue.put(task);
            // 2. 带超时等待
//            queue.offer(task, 500, TimeUnit.MILLISECONDS);
            // 3. 让调用者放弃任务执行
//            log.debug("放弃{}",task);
            // 4. 让调用者抛出异常
//            throw new RuntimeException("任务执行失败" + task);
            // 5. 让调用者自己执行任务
            task.run();
        });
        for (int i = 0; i < 5; i++) {
            int j = i;
            threadPool.execute(() -> {
                log.debug("{}",j);
            });
        }
    }
}

@FunctionalInterface // 拒绝策略
interface RejectPolicy<T>{
    void reject(BlockingQueue<T> queue, T task);
}

@Slf4j
class ThreadPool{
    // 任务队列
    private BlockingQueue<Runnable> taskQueue;

    // 线程集合
    private HashSet<Worker> workers = new HashSet<>();

    // 核心线程数
    private int coreSize;

    // 获取任务的超时时间
    private long timeout;

    private TimeUnit timeUnit;


    private RejectPolicy<Runnable> rejectPolicy;

    // 执行任务
    public void execute(Runnable task){
        // 当任务数没有超过coreSize时,直接交给worker对象执行
        // 如果超过coreSize时,加入任务队列暂存
        synchronized (workers) {
            if (workers.size() < coreSize){
                Worker worker = new Worker(task);
                log.debug("新增 worker 对象{},{}",worker,task);
                workers.add(worker);
                worker.start();
            }else {
                log.debug("加入任务队列{}",task);
                taskQueue.put(task);
                // 1. 死等
                // 2. 带超时等待
                // 3. 让调用者放弃任务执行
                // 4. 让调用者抛出异常
                // 5. 让调用者自己执行任务
                taskQueue.tryPut(rejectPolicy, task);
            }
        }
    }

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapcity);
        this.rejectPolicy = rejectPolicy;
    }

    class Worker extends Thread{
        private Runnable task;

        public Worker(Runnable task){
            this.task = task;
        }

        @Override
        public void run() {
            // 执行任务
            // 1) 当task不为空,执行任务
            // 2) 当task执行完毕,再接着从任务队列获取任务并执行
//            while (task != null || (task = taskQueue.take()) != null){
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null){
                try{
                    log.debug("正在执行....{}",task);
                    task.run();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    task = null;
                }
            }
            synchronized (workers){
                log.debug("woker被移除{}",this);
                workers.remove(this);
            }
        }
    }
}

@Slf4j
class BlockingQueue<T>{
    // 1.任务队列
    private Deque<T> queue = new ArrayDeque<>();
    // 2.锁
    private ReentrantLock lock = new ReentrantLock();
    // 3. 生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    // 4. 消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();
    // 5. 容量
    private int capcity;

    public BlockingQueue(int capcity) {
        this.capcity = capcity;
    }

    // 带超时的阻塞获取
    public T poll(long timeout, TimeUnit unit){
        lock.lock();
        try {
            // 将timeout 统一转换为纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()){
                try {
                    // 返回的是剩余时间
                    if (nanos <= 0){
                        return null;
                    }
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    // 阻塞获取
    public T take(){
        lock.lock();
        try {
            while (queue.isEmpty()){
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    // 阻塞添加
    public void put(T element){
        lock.lock();
        try {
            while (queue.size() == capcity){
                try {
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(element);
            emptyWaitSet.signal();
        } finally {
            lock.unlock();
        }
    }

    // 带超时时间的阻塞添加
    public boolean offer(T task, long timeout, TimeUnit timeUnit){
        lock.lock();
        try {
            long nanos = timeUnit.toNanos(timeout);
            while (queue.size() == capcity){
                try {
                    log.debug("等待加入任务队列{}...",task);
                    if (nanos <= 0){
                        return false;
                    }
                    nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("加入队列{}", task);
            queue.addLast(task);
            emptyWaitSet.signal();
            return true;
        } finally {
            lock.unlock();
        }
    }

    // 获取大小
    public int size(){
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }

    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否已满
            if (queue.size() == capcity){
                rejectPolicy.reject(this, task);
            }else {  // 有空闲
                log.debug("加入队列{}", task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        }finally {
            lock.unlock();
        }
    }
}

5.2 ThreadPoolExecutor

在这里插入图片描述

【1】线程池状态

        ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

状态名高3位接收新任务处理阻塞队列任务说明
RUNNING111YY
SHUTDOWN000NY不会接收新任务,但会处理阻塞队列剩余任务
STOP001NN会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING010--任务全执行完毕,活动线程为 0 即将进入终结
TERMINATED011--终结状态

        从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
        这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值

【2】 构造方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
  • corePoolSize 核心线程数目 (最多保留的线程数)
  • maximumPoolSize 最大线程数目
  • keepAliveTime 生存时间 - 针对救急线程
  • unit 时间单位 - 针对救急线程
  • workQueue 阻塞队列
  • threadFactory 线程工厂 - 可以为线程创建时起个好名字
  • handler 拒绝策略

工作方式
核心线程是一直存在的, 救急线程是有存活时间的
在这里插入图片描述

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
  • 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
  • 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
    • AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
    • CallerRunsPolicy 让调用者运行任务
    • DiscardPolicy 放弃本次任务
    • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
    • Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
    • Netty 的实现,是创建一个新线程来执行任务
    • ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
    • PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
  • 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。
    在这里插入图片描述

        根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池

【3】 newFixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

特点

  • 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
  • 阻塞队列是无界的,可以放任意数量的任务

评价: 适用于任务量已知,相对耗时的任务

@Slf4j
public class TestThreadPoolExecutors {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
            private AtomicInteger t = new AtomicInteger(1);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "mypool_t" + t.getAndIncrement());
            }
        });

        pool.execute(()->{
            log.debug("1");
        });
        pool.execute(()->{
            log.debug("2");
        });
        pool.execute(()->{
            log.debug("3");
        });
    }
}

【4】newCachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

特点

  • 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着全部都是救急线程(60s 后可以回收),救急线程可以无限创建
  • 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)

评价
        整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况

【5】newSingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

使用场景:
        希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

区别:

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
  • Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
    • FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
    • 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

【6】提交任务

// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
 throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
 long timeout, TimeUnit unit)
 throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
 throws InterruptedException, ExecutionException;
【6.1】execute
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        pool.execute(new Runnable() {
            @Override
            public void run() {
                log.debug("begin.....");
            }
        });
    }
【6.2】submit
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        Future<String> future = pool.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                log.debug("running.....");
                Thread.sleep(1000);
                return "ok";
            }
        });
        log.debug("{}", future.get());
    }
【6.3】invokeAll
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        List<Future<String>> futures = pool.invokeAll(Arrays.asList(
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        log.debug("begin");
                        Thread.sleep(1000);
                        return "1";
                    }
                },
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        log.debug("begin");
                        Thread.sleep(500);
                        return "2";
                    }
                },
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        log.debug("begin");
                        Thread.sleep(2000);
                        return "3";
                    }
                }
        ));

        for (Future<String> future : futures) {
            log.debug("{}", future.get());
        }

    }

【6.4】invokeAny
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        String result = pool.invokeAny(Arrays.asList(
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        log.debug("begin");
                        Thread.sleep(1000);
                        log.debug("end");
                        return "1";
                    }
                },
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        log.debug("begin");
                        Thread.sleep(500);
                        log.debug("end");
                        return "2";
                    }
                },
                new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        log.debug("begin");
                        Thread.sleep(2000);
                        log.debug("end");
                        return "3";
                    }
                }
        ));
        log.debug("{}",result);
    }

【7】关闭线程池

【7.1】shutdown
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
【7.2】shutdownNow
/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();
@Slf4j
public class TestShutDown {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<Integer> result1 = pool.submit(() -> {
            log.debug("task 1 running .....");
            Thread.sleep(1000);
            log.debug("task 1 finish ......");
            return 1;
        });

        Future<Integer> result2 = pool.submit(() -> {
            log.debug("task 2 running .....");
            Thread.sleep(1000);
            log.debug("task 2 finish ......");
            return 2;
        });

        Future<Integer> result3 = pool.submit(() -> {
            log.debug("task 3 running .....");
            Thread.sleep(1000);
            log.debug("task 3 finish ......");
            return 3;
        });

        log.debug("shutdown");
//        pool.shutdown();
//        pool.awaitTermination(3, TimeUnit.SECONDS);

        List<Runnable> runnables = pool.shutdownNow();
        log.debug("runnables{}",runnables);
        log.debug("other....");
    }
}

【8】任务调度线程池

        在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

【8.1】Timer
@Slf4j
public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();

        TimerTask task1 = new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                log.debug("task 1");
                Thread.sleep(2000);
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task 2");
            }
        };

        log.debug("start ......");
        // 使用 timer 添加两个任务,希望它们都在 1s 后执行
        // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);
    }
}

使用 ScheduledExecutorService 改写:

    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        pool.schedule(()->{
            log.info("task1");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1, TimeUnit.SECONDS);

        pool.schedule(()->{
            log.info("task2");
        },1, TimeUnit.SECONDS);
    }

scheduleAtFixedRate 和 scheduleWithFixedDelay例子:

    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        log.info("start....");
        // 一开始,延时 1s,接下来,由于任务执行时间 > 间隔时间,间隔被『撑』到了 2s
        pool.scheduleAtFixedRate(()->{
            log.info("running...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 1, TimeUnit.SECONDS);

 // 一开始,延时 1s,scheduleWithFixedDelay 的间隔是 上一个任务结束 <-> 延时 <-> 下一个任务开始 所以间隔都是 3s
        pool.scheduleWithFixedDelay(()->{
            log.info("running...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 1, TimeUnit.SECONDS);
    }

        整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队。任务执行完毕,这些线程也不会被释放。用来执行延迟或反复执行的任务

【8.2】正确处理执行任务异常

方法1:主动捉异常

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.submit(()->{
            try {
                int i = 1 / 0;
            } catch (Exception e) {
                log.error("error",e);
            }
        });
    }

输出:
在这里插入图片描述
方法2:使用 Future

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future<Boolean> future = pool.submit(() -> {
            int i = 1 / 0;
            return true;
        });

        log.info("result:{}", future.get());
    }

输出:
在这里插入图片描述

【8.3】 定时任务案例
@Slf4j
public class TestSchedule {
    // 如何让每周四 18:00:00 定时执行任务
    public static void main(String[] args) {
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        // 获取周四时间
        LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        // 如果当前时间 > 本周四, 必须找到下周四
        if (now.compareTo(time) > 0){
            time = time.plusWeeks(1);
        }
        // initailDelay 代表当前时间和周四的时间差
        // period 一周的间隔时间
        long initailDelay = Duration.between(now, time).toMillis();
        long period = 1000 * 60 * 60 * 24 * 7;
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleAtFixedRate(()->{
            log.info("running......");
        }, initailDelay, period, TimeUnit.MILLISECONDS);
    }
}

5.3 Fork/Join

【1】 概念

        Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型运算

        所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解

        Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率

        Fork/Join 默认会创建与 cpu 核心数大小相同的线程池

【2】 使用

        提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)RecursiveAction(没有返回值),例如下面定义了一个对 1~n 之间的整数求和的任务

@Slf4j
public class TestForkJoin {

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4);
        Integer result = pool.invoke(new MyTask(5));
        System.out.println(result);
    }
}

// 1-n之间整数的和
@Slf4j
class MyTask extends RecursiveTask<Integer>{

    private int n;

    public MyTask(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "{" + n + "}";
    }

    @Override
    protected Integer compute() {
        // 终止条件
        if (n == 1){
            log.info("join() {}", n);
            return 1;
        }
        MyTask t1 = new MyTask(n - 1);
        t1.fork();  // 让一个线程去执行此任务
        log.info("fork() {} + {}", n, t1);

        int result = n + t1.join(); // 获取任务结果
        log.info("fork() {} + {} = {}", n, t1, result);
        return result;
    }
}

改进

@Slf4j
public class TestForkJoin1 {

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4);
        Integer result = pool.invoke(new MyTask1(1,10));
        System.out.println(result);
    }
}

// 1-n之间整数的和
@Slf4j
class MyTask1 extends RecursiveTask<Integer>{

    private int begin;
    private int end;

    public MyTask1(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    public String toString() {
        return "{" + begin + "," + end +  "}";
    }

    @Override
    protected Integer compute() {
        // 终止条件
        if (begin == end){
            log.info("join() {}", begin);
            return begin;
        }
        if (end - begin == 1){
            log.info("join() {} + {} = {}", begin, end, end + begin);
            return end  + begin;
        }

        int mid = (end + begin) / 2;

        MyTask1 t1 = new MyTask1(begin, mid);
        t1.fork();  // 让一个线程去执行此任务
        MyTask1 t2 = new MyTask1(mid + 1, end);
        t2.fork();
        log.info("fork() {} + {} = ?", t1, t2);

        int result = t1.join() + t2.join(); // 获取任务结果
        log.info("fork() {} + {} = {}", t1, t2, result);
        return result;
    }
}

6. JUC

6.1 ReentrantLock 原理

`

【】

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值