高并发?syncronized原理?怎样自定义或是线程池?线程池参数如何合理配置? 并发编程 就决定用你了

    

一、前言与预备知识 

1.1为啥学

学习并发编程可能你正常工作几年都难用到,但是 想要往更好的方向走 这玩意是必须要学的

除非你想一辈子就在个小公司crud  很多中间件基本都用到并发编程,你想要了解它们的底层,得  学吧 然后这玩意尽管大部分公司工作中用的少 但是中高级以上级别面试可是必问的 

1.2 进程与线程的概念

一个父 一个子 子是最小单位

 

并发 

                 

并行

 

1.3 同步与异步的概念

如下两图 在同一个线程里

图一 先来后到 代码按顺序走完就是 同步 图二 代码在两个线程里 自己跑自己的  就是异步

 

 

 记住:单核cpu下,多线程并不能提升效率,反而会增加线程之间的切换 多核的情况下,可以在不影响运行结果的情况下 想办法将任务拆分 交由多个线程执行  

 

 二、线程创建的几种方式

2.1 Thread

2.2 Runnable 配合 Thread

可以看Thread的源码中的run方法如下 如果target不为空 则执行 target的run方法 而target对象

就是 Runnable 的实例,在 Thread构造时可传入

优先组合 然后才是继承 可以解耦合 

2.3 FutureTask 配合 Thread

可以看到 FutureTask 是 Runnable子类 

 然后它里面是有个 Callable对象 调用thread的run方法时 会调用 FutureTask里的 Callable实例的call方法 然后放到 result中

 调用get方法时 get方法内会一直判断 当前任务执行的状态 如果是 已完成 则 得到结果 强转为泛型 并返回 否则阻塞住

三、进程与线程运行情况 及 查看 与 杀死 相关命令

3.1 windows 中

 一般上面tasklist用的不多 我们可以使用jps查看java进程 然taskkill杀死

 

 或者 在任务管理器中找到,然后右键 结束任务

3.2 linux 中

3.2.1命令概览

 一般 用管道符过滤     | grep 来查看java进程

 

 找到后 kill pid 杀死对应进程

3.2.2 top命令实时查看进程线程的运行时间及cpu占比等信息

top命令执行后能看到当前所有运行的进程 及 cpu 的消耗情况

 我们一般用法就是 先找出java相关进程

 再找到该进程下 对应线程运行情况     top -H -p 102431

这个命令 top 命令可以实时查看 进程线程 的 运行时间及cpu占比 

3.2.3 jstack 查看某一时刻 某进程中所有线程的状态及运行信息

如果想要查看当前这一时刻的进程的所有线程更详细信息

可以使用jstack pid

3.3 jconsole图形化界面 

jconsole 可以检测linux与windows中java程序运行情况

3.3.1  linux环境下模拟程序运行并添加 监控配置

3.3.2  关闭防火墙才能用本地jconsole连接

3.3.4本地启动jconsole并连接

 3.3.5 jconsole图形界面中查看java线程运行情况

四、线程运行原理

4.1 初步了解线程运行

如下6张图 注意看右上角 左下角 及 右下角 

刚开始 运行main方法 做小姐创建一个栈帧 存放 main方法运行的信息 右下角 是当前运行栈帧的参数变量  -》第二张图 运行method1方法时可以看到左下角多了一个栈帧 并且活动栈帧也变成了method1所处栈帧 -》第三张图 运行method2 也与 method1 一样 -》再然后第四张图 method2 运行完后返回到method1末尾 此时 运行method2的栈帧出栈 栈空间被回收 -》图五 也一样 =》直到最后 main方法所处栈帧出栈 

总结 : 方法的执行信息是在一个栈内的某个栈帧上的 ,并且一个线程只有一个活动栈帧,为什么是栈呢? 先进后出 符合方法的执行顺序与 栈帧空间chu回收顺序

 

 

 

 

4.2 深入了解线程运行

线程的运行 大致涉及了下面这些角色:CPU,堆,栈 ,方法区 

首先 会执行类加载 将类文件的 二进制字节码文件加载到虚拟机中 存放在方法区 图中是为了好理解,其实存放的是二进制字节码

再然后一个线程被创建   由cpu调度执行 这里比上面多了些信息

程序计数器:下一步 运行二进制字节码的行数 cpu从程序计数器拿到后调度执行

返回地址: 记录当前方法执行完 应该执行的下一行代码的位置 

:所有new出来的 对象的值都放在堆中进行管理 堆 也是jvm内容部分最为关键的研究对象

锁记录与操作数栈 后面会在 锁部分继续补充 循序渐进

那么 想比与4.1的初步了解,现在我们已知的更深入了解的线程运行的步骤就变成了:首先 一个方法被调用时就被分配一个栈帧 程序计数器指向第一行代码 然后 cpu得到执行的代码的位置 调度执行 执行的初始返回地址就有值了, 执行过程 中 局部变量表内容开始增加 如果局部变量的值是被new出来的,那么 它的值对象 会被放在 堆空间 局部变量表只存放变量名 与 x=10 这类常量信息 当某方法执行完后程序计数器会指向当前方法的返回地址 该方法所处栈帧被弹出,栈帧空间被回收 直到方法运行完 

 4.3 线程的上下文切换

简单来说 从cpu的某个核从 一个线程的执行 切换到另一个 线程的执行 就叫线程的切换 

上下文是啥呢? 就是线程栈内 信息 这些信息在线程执行完之前都会一直保存着 包括 程序计数器,局部变量表等等信息 线程的上下文切换是有性能消耗的 上下文切换的被动主动时机 如下图及更多详细信息如下图

4.4 怎样断点 调试 多线程

新建断点 断点上右键 选中 Thread 就可以在线程模式下 断点调试

 

 如下debug运行后 可以左下角看到 有两个线程 并且他们此时的状态都是运行中

点击任一一个都可以 进入到当前线程执行界面去调试

五、常见的方法

5.1概览

常见的方法概览如下

 

5.2start 与 run 方法

5.2.1 start 与 run 方法的区别

如下两图是调用 run方法 与 start方法的比较 可以看到控制台 中括号内,直接调用run方法还是在当前main线程内执行,而调用start方法,相当与新开了一个线程然后执行run方法 新开的线程与当前main线程的执行 互不干扰

5.2.2 执行start方法前后 线程的状态  

可以看到 状态由 NEW 变成了RUNNABLE new是刚创建出来的状态,runnable是就绪状态可被cpu调度执行

5.2.3 执行两次start方法会?

会报错,同一个线程 start方法 只能调用一次

5.3 sleep 与 yield 方法

sleep 是让当前线程休眠(此时这个休眠是有时限的)因为sleep方法必须传参 ,正常休眠结束后 线程会从timed waiting状态转变为 runnable状态 从图二可以看到休眠状态下的线程被打断后 会抛一个被打断异常 它的状态也是变成了runnable 就绪状态

yield好比是让座,我本身运行着 然后变成 runnable就绪状态 其他同优先级的线程可以占座变成运行状态,如果没有同优先级的线程,那么结果是随机的,可能我还是继续运行 也可能别人运行

5.4线程的优先级 

5.5 sleep小应用

当我们在 某个线程的 执行代码内加 while(true) 代码时 一定要记得在里面加 sleep 进行短暂休眠

 如下两图 分别是 在 while( true) 代码内 没加sleep 与加了的 cpu占比 区别 ,前者直接快占满 

5.6 join方法

5.6.1不带时效的join方法

如下两图 图一没使用join 图二使用了join, r为全局变量 主线程 需要显示他t1线程运行后的r的结果 ,那么就必须要等 t1线程执行完 再 输出 r 而join方法也正是 起到这样的作用 调用join方法的线程   会立马处于第一优先级 执行完这个线程 后 才会继续执行其它线程   图二中 因此主线程中打印的r的结果为 10 

 

join会抢占cpu 然后其他线程如果是timedwaiting状态 那么 没影响 如果是 运行状态,那么就被抢过时间片 然后等着 所以两张图 总执行的时间都是以执行最长时间的那个线程为准  2秒

 

5.6.2带时效的join方法

没带失效的join就好比是 你开了挂,cpu必须先执行完你才能执行其它线程  ,带了失效的join方法如下图 就比如你这挂有时效 1.5秒这个时间段内 cpu必须执行你 但是 1.5秒过后cpu又是一视同仁

5.7 interrupt方法

调用了 sleep,wait,join等方法 的线程 被打断时 会抛InterruptedExeption 然后isInterrupt标记还是为false 处于这些状态被打断的线程 我们可以在 catch中做处理 如果不做处理 被打断的线程会回到 runnable就绪状态 等待被分配到cpu的时间片后再重新执行

 正常运行的线程被打断 时 它不会抛异常 而是isInterrupt被打断标记 被置为true 比如 下图 死循环内 我们 可以判断 isInterrupt的值然后做处理 是结束线程运行还是继续

 5.8 两阶段终止 - interrupt

下面两图代码模拟的是一个监控程序  监控程序每次执行前会判断打断标记 如果被打断了则料理后事然后停止监控程序的运行 否则休眠一秒后 执行监控并记录 如果在休眠或者监控记录的过程中 被打断 这个时候会被catch捕获 这时被打断状态仍未false(前面说了)  而线程状态为runnable就绪状态  所以这时要重新打断 就绪状态(非sleep,wait,join)的线程被打断 线程打断标记是会被置为 true的所以下次循环时 会料理后事 然后程序终止

 

 

5.9 isInterrupted方法与 interrupted方法

5.9.1 两者异同

如下,这两个方法都可以判断 线程是否被打断 但前者是非静态方法 后者是静态的 然后 前者不会清楚打断标记 后者会清除打断标记  如果你要判断后 清除打断标记就用后者

5.9.2使用park方法时的选择

打断标记为true时会让park方法失效 

先看看图一 park方法的作用 如下图 执行park方法后当前方法就被阻塞住了 再看看图二当线程由于调用park阻塞时 调用interrupt打断就可以让该线程继续运行

 但是 当 isInterrupt状态为true时 park方法不生效 如图下图一 

 这时 如果需要park生效就可以使用前面说的interrupted()静态方法 它会在获取到打断标记后将其置为false 因此后面的park才能生效

5.10 不推荐使用的方法 

他们都过时了,因为一些弊端 比如 stop()停止线程会导致锁不释放 其它线程获取不到锁就凉凉

5.11 setDiaemon 守护线程

可以理解 Diaemon是辅助程序 其它的线程 都执行完 不管 守护线程咋样 都会结束进程 

也相当于运动会的陪跑 参赛者跑完了 比赛就结束 谁管你 陪跑

5.12 统筹规划习题

 现在有这样一道题目,有老王跟小王 他们想喝茶了

老王只负责烧水 耗时五秒

小王 负责洗杯子 拿茶叶 泡茶 分别耗时 2,3,2 秒

用代码模拟 怎样让他们最快喝到茶 

代码如下 主要用到了sleep 与 join俩方法 最快时间就是 烧水加泡茶的时间 大概七秒

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.test1")
public class Test1 {

    public static Thread threadXiaoWang = null;
    public static Thread threadLaoWang = null;
    public static long start;

    public static void main(String[] args) throws InterruptedException {

        start = System.currentTimeMillis();

        threadXiaoWang = new Thread(() -> {
            try {
                method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
         threadLaoWang = new Thread(() -> {
            try {
                method2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadXiaoWang.start();
        threadLaoWang.start();
    }

    private static void method1() throws InterruptedException {
        log.info("洗杯子");
        TimeUnit.SECONDS.sleep(3);
        log.info("洗完杯子");

        log.info("拿茶叶");
        TimeUnit.SECONDS.sleep(2);
        log.info("拿到茶叶");

        threadLaoWang.join();

        log.info("泡茶");
        TimeUnit.SECONDS.sleep(2);
        log.info("泡完茶了,总共耗时{} ms",System.currentTimeMillis() - start );

    }

    private static void method2() throws InterruptedException {
        log.info("烧水");
        TimeUnit.SECONDS.sleep(5);
        log.info("烧完水了");
    }

}

六、线程状态

6.1 操作系统层面

6.2  java API层面

 

 

6.3 代码

线程刚new出来 NEW状态

线程处于 就绪,运行或者IO阻塞时 都是runnable状态

 

因为执行后 后面主线程有五秒休眠 所以那时候 t3是终止 terminated状态

 

timeed waiting 状态

 

join线程占据cpu时间片 所以 t5等他执行完才能执行 是 waiting 等待状态

 

因为锁被 t4持有一直没释放 所以 t6是阻塞状态

 

 

 

七、共享模型之管程

7.1 共享问题

 如下为代码演示

 原因 看似 count ++只有一行代码 但是 它在jvm层面 是由如下4步组成 所以 当我count为5时

准备 加1 执行到一半 被 减一的线程抢占cpu 然后那边开始减一 正常加一减一结果应该还是5,

但是由于 先减一 此时 count变为 4 而 加一操作那边count的值还是5然后进行加一 最终 结果count就变为6了 

 

7.1 临界区

 以上问题 我们一般描述为 在临界区 出现静态条件 导致

7.2 syncronized

7.2.1  syncronized初步使用

这是个啥东西呢? 同步的意思 也就是常说的锁的一种 

如下代码 与运行结果 为0 

@Slf4j(topic = "c.test1")
public class Test1 {

    public static Thread threadXiaoWang = null;
    public static Thread threadLaoWang = null;
    public static long count;

    public static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

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

        threadXiaoWang.start();
        threadLaoWang.start();

        TimeUnit.SECONDS.sleep(3);
        log.info("计算后的count的值为{}",count);
    }
}

 得到正确结果

那为啥加了syncronized就好使了呢? 

简单来说 它其实就是在锁对象内做了某个标记 标记里面有执行当前 syncronized代码块的 线程id,若是代码块内的代码没有运行完 被别的线程抢占到了cpu那么 判断 当前线程与锁对象内的线程是不是同一个 不是就被阻塞 然后 运行完代码块后 锁对象内的 信息会被清除 这样两个线程又可以公平竞争了 这样就保证了 程序最终得到正确的结果

  

7.2.2  syncronized问题思考

上面简单说了syncronized 的原理 后面还会继续补充 那么就下面几个问题 做下思考

 问题1:放在for循环外面 

这样也能保证正确结果 但是 程序运行情况就是 先 做5000次自增 再做5000次自减或者相反

 问题2:锁不同对象

这样起不到锁的作用, 最终结果还是错误结果 不是 0 因为锁对象上有持有线程信息

 问题2:只有一个线程内加了syncronized锁

起不到锁的作用 只有一人持有 不影响另一方的执行 与不加锁情况一样

syncronized加在普通方法上

syncronized加在静态方法上

 

 加syncronized 好比 在门口排队

7.3 线程安全分析

7.3.1线程八锁

以下八张图 为 线程八锁  想想控制台的打印结果吧,这里就不贴答案了

提示: 锁住同一对象才 有用 锁住不同对象等于没锁

 

 

 

 

 

 7.3.2 变量共享是否产生线程安全问题

多个线程共享同一变量会导致线程安全 不共享则不会 (后面会引入原子类解决这一问题)

7.3.3 线程安全分析习题

判断以下示例是否线程安全

1.不安全 ,安全 ,安全 ,不安全 ,不安全   

String类不可变类 Date虽然声明为final但是

其内成员变量被共享 可能产生线程安全问题

 2. 不安全

Servlet类单例  所以userService实例被共享 然后count变量 作为共享变量 可能产生线程安全问题

 3.不安全

MyAspect没加 scope注解 所以默认单例 然后 start作为成员变量被共享,

当before方法被多个线程调用时 start 变量 在after方法内可能得到错误的值    所以线程不安全

 线程安全

 线程不安全 与上面类似 conn对象 被多个线程共享 可能一个线程 得到 conn连接了,此时时间片被另一个抢过去 将 conn可能直接关闭  

线程安全 但是写法不合适 userDao这种对象一个就够了 Connection对象一般声明为局部变量 避免 共享后其它线程的修改 

 不安全 sdf对象被传入某个方法中 方法中的代码未知 所以 可能存在多个线程操作的情况

7.4 Monitor

7.4.1 对象头

  引用类型 值的存储 是在如下的对象里面的 除了 存储值外 对象里还有 一个对象头的东东 如下可以看到 而我们现在关注的就是对象头的前半部分Mark Word(看意思这部分是做标记信息用的) 后半部分是字节码

 数组对象的对象头中除了刚刚说了 除了 mark word  ,字节码 还有个存数组长度的32位的空间

 其中mark word 的结构如下 

各种状态的  mark word 存储的内容可以看到是不同的 

 普通状态下 :有其hash码 分代年龄 偏向锁的标记

偏向锁状态下 :有偏向的线程id 分代年龄 偏向锁的标记 还有个epoch(批量重偏向次数)

epoch的概念(这个东西比较偏,这里可能比较难懂,看看就好 后面还会说到 偏向锁 轻量级锁 等的概念 ) : 时期时代的意思 记录重偏向的次数 当达到某一阈值时 会考虑该对象不再适合偏向锁

这个玩意 不止在对象的mark word 中有 在 class对象上也有记录 ,我们都知道 当临界区的代码 第一次 被访问时 此时 对象 会变成 偏向的状态 也就是说只要有一个线程获得了锁那么 锁就进入了偏向模式 然后 当 有不同的线程来尝试获得锁时 此时 对象尝试 将当前锁对象 的对应的线程id 置为 当前访问线程 也就是转化为轻量级锁这样一个状态 如果这一过程失败 (被其它线程抢到了锁) 那么 锁对象转化为 重量级锁状态 

当某类当有大量实例对象 在 某方法 中同时被作为锁 比如 有锁对象 o1,o2,o3,on 使用时 

如果线程一 执行完所有包含 上面 那些锁的代码后  这时候 有线程二 来访问此方法 那么 这个时候从下图可以看到 我们 锁对象全部都要进行一个批量重偏向 而 epoch就是记录 批量冲偏向次数的,他在对象mark word中存一份 class信息中也存一份 发生一次批量重偏向 class中的 epoch+1 所有该类已存在对象的epoch中也+1 然后线程id也会被记上 下次 访问时 如果对象锁中epoch  不等于 class信息中的epoch数 说明class 对象已经换代了那么直接 记录线程指针指向当前线程 

7.4.2 syncronized工作原理

原理如下图 

简单说明:当线程一访问同步代码获取到 锁时会   monitorenter 就是给对象绑定个 监视对象 monitor 

monitor对象中有个 EntryList 那是在线程一还未执行完 同步代码时 另一个线程如果访问 那么就被阻塞,然后他们被放在 EntryList中排队 线程一执行完 后 EntryList中的线程就可以抢到锁继续执行了 WaitSet涉及到 wait notify 后面再说

7.4.3 syncronized工作原理 (字节码角度)

如下可以看到 syncronized 主要是 由 俩命令 monitorenter 与 monitorexit构成 

当第一个线程访问同步代码获取到 锁时会   monitorenter 就是给对象绑定个 监视对象 monitor 

当结束同步代码 或者 产生异常时 执行 monitorexit 也就是取消 锁对象与 monitor绑定 

注:这里是 jdk1.6前的syncronized 原理 后续有做优化

7.5syncronized优化原理

7.5.1小故事

 

7.5.2 锁对象状态 概览

在jdk1.6后syncronized陆续得到了优化 ,以前的锁(我们之前说的锁对象绑定monitor)相对于 现在的来说是重量级的,先看看下图 看懂了syncronized原理 你就基本懂了 由于 syncronized是针对对象锁的

现在可以看到对象锁的状态分为 下面五个状态 分别是

普通的无锁状态   被创建出来的对象,该对象 心灵纯净 没有被当作锁使用过

偏向锁状态   对象默认都是可偏向的,只是程序启动后有点点延时,可偏向的标记是后3位位101,当出现 第一个线程进入同步代码块时 锁对象就由上面的无锁状态变为了 偏向锁状态,可以看到

hashcode没了 前25位也被占用了 这56位 前54位放了线程id 后 2位放 epoch分代 当  线程一运行结束,由于 偏向锁 在同步代码运行完后 还是偏向状态 线程id不会去除 所以 如果线程二再次访问同步代码块 此时锁对象 变为 轻量级锁状态线程栈中会生成一个锁记录 锁记录里的内容就是 

 轻量级锁状态 markword加上 锁记录它自己的地址 然后与 对象里的markword交换  释放的时候就再换回去 重入时 新生成的锁记录 里面内容是null

重量级锁状态 前面说的两种锁都是无锁竞争的时候 的状态,当有一个线程占用锁 时 另一个线程此时要竞争 这时锁对象就变为了 重量级锁状态 

GC标记状态 当程序运行结束 锁对象无用后 被 GC标记时的状态

7.5.3 轻量级锁加锁过程

 

 

 

 

 7.5.4 重量级锁竞争之自旋优化

 

7.5.5 偏向锁

因为偏向锁总是要生成锁记录 然后 cas交换 并且 当撤销时又要换回来 并且删除锁记录 所以

在没有竞争 一直是一个线程访问同步代码块时 这时候显然就没有必要 生成锁记录 然后cas交换这样,于是 java 6中进行了优化 引入了一个偏向锁的玩意  它只需要简单的记录 下偏向的线程id

还有个批量重偏向的阈值 当 达到 重偏向次数达到 19次时 epoch +1 此时再由轻量级锁转换位偏向锁 偏向id设置位当前 线程id 

 

 

 

7.5.6 偏向锁撤销 向 轻量级锁转换-代码示例

如下两图 是main方法内的代码  可以看到 有俩线程 后面那线程直接 wait 等待 等前面那线程执行完后才被唤醒

第一次打印 markword 这个时候锁对象才被new出来 可以看到 末尾是101   说明普通状态但默认是可偏向的 因为前面都为0 说明还没偏向

第二次打印 这个时候获取到锁 了 除了后面为101外 最前面有记录偏向的线程id

第三次打印 这个时候同步代码结束运行 锁释放 可以看到 与第二次打印的信息还是一模一样,还是偏向状态 想想理由是啥,我猜这样也正方便了锁重入

第四次打印 这个时候已经到了第二个线程了 这个时候还啥也没做,所以打印结果跟之前一样

第五次打印 这个时候 是第二个线程接管了锁  所以后面变为 00 轻量级锁状态 然后 前面 62位都记录的轻量级锁的锁记录 

第六次打印 这个时候 退出了同步代码块 可以看到 锁记录清空了 然后 对象变成了不可偏向的状态

那它啥时候变成可偏向状态呢?这个得19次之后 如果在二图代码中 syncronized外整个for循环 只要大于 19次 那么 第二十次对象就变成了偏向锁状态然后 线程id是当前线程

 控制台打印结果

7.5.7 hashcode方法导致  偏向锁被撤销

除了前面的当另一个线程获取锁时 锁的偏向状态会被撤销,还有调用hashcode方法时 偏向状态也会被撤销 为啥呢? 可以从前面看到 偏向锁状态时,记录线程id都得54位 而记录hashcode也得31位 总共也就64位 那么这个时候就无法使用偏向锁了,但是轻量级重量级锁不会

设计如此,可能设计者觉得撤销偏向锁影响不大并且这类场景很少见吧

7.5.8批量重偏向

如下两段代码,线程t1中 有三十个不同的dog对象都被加了偏向锁 ,在 线程t2中  前面19个对象会被成轻量级锁状态,超过19个后 后面 第二十个时,发现还是当前线程  当前锁对象就变为偏向锁 后面 10也都变成偏向锁 偏向的线程id就是t2的id 这就是批量重偏向

 

7.5.9批量撤销

 

7.5.10 锁消除

啥意思呢?就是你加了无用的锁,也就是加了等于没加 那么 JIT编译器优化时 就会把它去掉

可以看到 上下俩方法 使用Benchmark性能测试后 得分基本一样 单位是纳秒

 但是当你把自动锁消除 的优化 去掉后

得分就为如下了 差距十几倍

7.6 wait/notify

当某线程 拿到锁后 却发现自己没满足同步代码块中的某个条件 无法继续运行此时需要让出cpu 那么 用 sleep就不行 sleep是还会占用当前cpu 这里就可以考虑使用 wait了(还有个park后面说)

当满足条件 时 唤醒 因wait而阻塞的线程 该线程即可 重新回到 就绪状态了

常用的代码结构如下

7.7 设计模式

7.7.1 保护性暂停模式

 初版(不符合面向对象):

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.test1")
public class Test1 {

    public static void main(String[] args){

        GuardedObject guardedObject = new GuardedObject();

        new Thread(()-> {
            try {
                Object response = guardedObject.get(2000);
                log.debug("获取到response : {}",response);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()-> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.set("谭警官");
        }).start();
    }


}

@Slf4j
class GuardedObject{

    private Object response;


    public Object get(long milliseconds) throws InterruptedException {

        long beginTimeMillis = System.currentTimeMillis();
        long costTimeMillis = 0;
        while(response == null){
            synchronized (this){
                if(costTimeMillis < milliseconds){
                    this.wait(milliseconds - costTimeMillis);
                }else{
                    log.error("等待超时 response 为 {}",response);
                    return response;
                }
                costTimeMillis =  System.currentTimeMillis() - beginTimeMillis;
            }
        }

        return this.response;
    }

    public void set(Object response){

        this.response = response;
        synchronized (this){
            this.notifyAll();
        }
    }

}

 优化版(符合面向对象):

自行脑部

7.7.2 生产者消费者模式

如下为参考代码 理解 wait notify 用法后 合理应用即可实现

package com.robert.concurrent.test;


import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.test1")
public class TestMessageQueue {

    public static void main(String[] args) throws InterruptedException {

        MessageQueue messageQueue = new MessageQueue(3);

        new Thread(()->{
            try {
                for (int i = 0; i < 10 ; i++) {
                    Message message = new Message("" + i, "value" + i);
                    messageQueue.product(message);
                    log.debug("成功生产消息:{}", message);
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        TimeUnit.SECONDS.sleep(5);

        new Thread(()->{
            try {
                for (int i = 0; i < 10 ; i++) {
                    Message message = messageQueue.consume();
                    log.debug("成功消费消息:{}", message);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();



    }


}


@Slf4j
class MessageQueue{

    private LinkedList<Message> messageList = new LinkedList();

    public MessageQueue(int queueSize) {
        this.queueSize = queueSize;
    }

    private int queueSize;

    public void setQueueSize(int queueSize) {
        this.queueSize = queueSize;
    }

    public Message consume() throws InterruptedException {
        Message message = null;

        synchronized (messageList){
            while(messageList.size() == 0){
                messageList.wait();
            }
            message = messageList.remove();
            log.debug("获取到消息 {}", message);
            TimeUnit.SECONDS.sleep(1);
            messageList.notifyAll();
        }

        return message;
    }

    public void product(Message message) throws InterruptedException {

        synchronized (messageList){
            while(messageList.size() == queueSize){
                messageList.wait();
            }
            messageList.add(message);
            TimeUnit.SECONDS.sleep(1);
            log.debug("生产消息 {}", message);
            messageList.notifyAll();
        }
    }

}

@ToString
class Message {

    private String id;
    private Object value;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Message(String id, Object value) {
        this.id = id;
        this.value = value;
    }
}

7.8 park 与 unpark

7.8.1 park 与 unpark 代码示例

简单说下 park 与 notify样也可以起到阻塞 某线程的作用  unpark也 与 notify是唤醒线程的作用

如下四图 

先 park 后unpark

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestPark {

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {

            try {
                TimeUnit.SECONDS.sleep(1);
                log.debug("线程thread1 park 休息");
                LockSupport.park();
                log.debug("线程thread1 继续运行");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                log.debug("唤醒线程thread1");
                LockSupport.unpark(thread1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread2.start();

    }

}

运行结果如下

 

 先 unpark 后park

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestPark {

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {

            try {
                TimeUnit.SECONDS.sleep(2);
                log.debug("线程thread1 park 休息");
                LockSupport.park();
                log.debug("线程thread1 继续运行");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                log.debug("唤醒线程thread1");
                LockSupport.unpark(thread1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread2.start();

    }

}

 运行结果如下

7.8.2 park,unpark  与  wait,notify的区别

简单概括下下面几点 park 与 unpark 针对的对象是 线程  所以它可以精准的唤醒或者使某个线程阻塞等待 而 wait notify 主要是针对锁对象 ,为啥 unpark能提前唤醒呢?继续看

7.8.3 park,unpark原理

简单概括下 park使得 counter变量变为1 而 unpark执行时会判断 counter是否为1 为1就不阻塞直接继续运行 所以 能提前执行unpark    而  counter为0时执行 park 就会阻塞  并且 unpark   唤醒线程使其重新执行后 还是 会把  counter置为0  下面这个小故事版本比较通俗易懂

 

 

7.9 线程状态转换

前面说了java内线程有这么6种状态

NEW: 在java内被new出来的 还未与 操作系统中的线程关联

RUNNABLE :

runnable又包含三种状态

可运行状态:调用 start方法 就处于可运行状态 此外 还有 sleep睡眠时间结束,wait阻塞被notify唤醒时 interrupt被打断时,处于 blocked状态竞争到cpu时 都会进入该状态 

运行状态:这个不用说 cpu时间片 转到了 正在执行中

阻塞状态:这里的阻塞指的是io阻塞

BLOCKED: 处于此状态的 线程 是准备着占有锁的线程运行完,就去争抢CPU

waiting :处于此状态的 线程需要外界条件唤醒 比如 interrupt ,noitify/notifyall ,unpark

TERMINATED : 线程运行结束就处于此状态

以下是一些具体场景的 线程状态转换的举例

 

 

 

7.10 多把锁

不同业务方法 可以考虑使用多把锁 ,下图一 只用一把 锁明显不合理 所以改为图二

 

7.11 活跃性

7.11.1死锁现象

如下代码为死锁代码示例 当两个线程都在等待某把锁 而锁需要对方 释放 而  陷入的 双方都无法继续运行的情况 称为死锁

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestDeadLock {

    public static void main(String[] args) throws InterruptedException {

        Object lockA = new Object();
        Object lockB = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lockA){
                log.debug("线程thread1 获取到lockA");

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lockB){
                    log.debug("线程thread1 获取到lockB");

                }
            }
            log.debug("线程thread1 运行结束");

        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            synchronized (lockB){

                log.debug("线程thread2 获取到lockB");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lockA){
                    log.debug("线程thread2 获取到lockA");
                }
                log.debug("线程thread2 运行结束");
            }

        });
        thread2.start();

    }

}

7.11.2定位死锁的方法

1.使用 jps 然后 jstack 进程id 可以看到如下死锁信息 

 2.使用 可视化工具 jconsole

选择连接对应进程 然后点击检测死锁

 

 

7.11.3饥饿现象

之前不是发生死锁嘛,想到一个顺序加锁的方式来解决 可是会导致如下饥饿现象 也就是 cpu冷落某一或几个线程 它们执行次数太少  代码示例就不整了 就是加锁顺序调下 这种饥饿现象 的解决

可以继续看下节 将会解决

 

7.12 ReentrantLock

7.12.1简介

 7.12.2基本语法

 7.12.3 可重入特性

如下代码示例,看到程序正常运行结束 可以知道所谓可重入锁 就是 同一线程内 可以 多次获取同一锁

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.test1")
public class TestReentrantLock {

    static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args){

        Thread thread1 = new Thread(() -> method1());
        thread1.start();

    }

    private static void method1() {

        try{
            reentrantLock.lock();
            method2();
            log.debug("method1 运行结束");
        }catch (Exception e){
        }finally {
            reentrantLock.unlock();
        }

    }

    private static void method2() {
        try{
            reentrantLock.lock();
            log.debug("method2 运行结束");
        }catch (Exception e){
        }finally {
            reentrantLock.unlock();
        }
    }

}

7.12.3 可打断特性

这里就稍微提下

 reentrantLock.lock() 是不能被打断的

reentrantLock.lockInterruptibly() 可以被打断 避免一直获取不到锁而等着 可以解决死锁问题d打断方式如下  thread1.interrupt();

reentrantLock.lockInterruptibly() 用 InterruptedException catch 下 与 前面 wait,sleep,join等的打断基本一样 

7.12.4 锁超时

这个也不多说 就是调用 tryLock 替代了前面 lock 与 lockInterruptibly 

如下,这两种 一个没带超时时间 就是获取到锁继续往下走  另一个是固定时间段内 获取到锁就继续往下走

7.12.5 死锁问题解决

示例代码如下 使用 tryLock  不断尝试 尝试 不成功 就 finally内释放另一个锁 这样 就 不会

占着一把锁的同时 死等着另一把锁  

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.test1")
public class TestDeadLock {

    public static void main(String[] args) throws InterruptedException {

        LockA lockA = new LockA();
        LockB lockB = new LockB();

        Thread thread1 = new Thread(() -> {

            try{
                while(true){
                    if(lockA.tryLock()){
                        log.debug("线程thread1 获取到lockA");

                        try {
                            TimeUnit.NANOSECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        try{
                            if(lockB.tryLock()){
                                log.debug("线程thread1 获取到lockB");
                                log.debug("线程thread1 执行业务代码");
                                try {
                                    TimeUnit.NANOSECONDS.sleep(1);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                break;
                            }
                        }finally {
                            lockA.unlock();
                            log.debug("线程thread1 解锁lockA");
                        }
                    }
                }
            }finally {
                lockB.unlock();
                log.debug("线程thread1 lockB");
            }
            log.debug("线程thread1 运行结束");
        });

        Thread thread2 = new Thread(() -> {
            try{
            while(true){
                    if(lockB.tryLock()){
                        log.debug("线程thread2 获取到lockB");
                        try {
                            TimeUnit.NANOSECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        try{
                            if(lockA.tryLock()){
                                log.debug("线程thread2 获取到lockA");
                                log.debug("线程thread2 执行业务代码");
                                try {
                                    TimeUnit.NANOSECONDS.sleep(1);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                break;
                            }
                        }finally {
                            lockB.unlock();
                            log.debug("线程thread2 解锁lockB");
                        }
                    }
                }
            }finally {
                lockA.unlock();
                log.debug("线程thread2 解锁lockA");
            }
            log.debug("线程thread2 运行结束");
        });

        //添加join方法是为了 其它线程都执行完 才结束 main方法 不然 打印不全
        thread1.start();
        thread2.start();

        try {
            TimeUnit.NANOSECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

class LockA extends ReentrantLock{

}

class LockB extends ReentrantLock{

}

7.12.6 公平锁

公平锁是个啥概念呢? 当 当前 占用cpu的线程释放 cpu后  处于 blocked状态的这些线程就会去争抢cpu 谁先抢到算谁的 那这样就是不公平的 毕竟阻塞顺序可能有先后  我最先进来 阻塞的队列 反而我最后 抢到cpu然后执行 syncronized就是不公平的 

reentrantlock也是默认不公平的,但是它可以设置成公平的 构造方法内传true即可,但是这样是挺耗性能的所以不建议用,后面谈及源码时会说到

7.12.7 条件变量

跟之前的wait notify挺像 之前是针对 锁对象wait 现在是针对 Condition对象wait

而一个锁对象可以有多个condition

示例代码如下  记住 finally 中一定要解锁 避免发生死锁 或者锁不释放其它线程获取不到

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.test1")
public class TestReentrantLock {

    static boolean hasCigarrete = false;
    static boolean hasTakeOut = false;
    static ReentrantLock reentrantLock = new ReentrantLock();
    static Condition hasCigarretecondition = reentrantLock.newCondition();
    static Condition hasTakeOutCondition = reentrantLock.newCondition();

    public static void main(String[] args) throws InterruptedException {

        new Thread(() ->{
            try{
                reentrantLock.lock();
            while(!hasCigarrete){
                log.debug("小南发现没烟 无法干活 等待中");
                hasCigarretecondition.await();
            }

                log.debug("小南干活");

            }catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
                log.debug("小南干活完毕");
            }

        },"小南").start();

        new Thread(() ->{
            try{
                reentrantLock.lock();
                while(!hasTakeOut){
                    log.debug("小红发现没外卖,肚子饿 无法干活 等待中");
                    hasTakeOutCondition.await();
                }

                log.debug("小红干活");

            }catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
                log.debug("小红干活完毕");
            }

        },"小红").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() ->{

            try{
                reentrantLock.lock();
                hasCigarrete = true;
                log.debug("烟送到了");
                hasCigarretecondition.signal();
            }finally {
                reentrantLock.unlock();
            }

        },"送外卖的").start();

        new Thread(() ->{

            try{
                reentrantLock.lock();
                hasTakeOut = true;
                log.debug("外卖送到了");
                hasTakeOutCondition.signal();
            }finally {
                reentrantLock.unlock();
            }
            },"送烟的").start();



    }

}

运行结果如下  

7.12.8 设计模式 固定运行顺序

现在有个题目 有线程t1与线程t2 线程t1内打印 1 线程t2内打印 2  ,正常 启动俩线程时 我们无法控制其执行顺序 我现在想要 先打印2 再打印 1 要咋做呢?

 wait 、notify版本代码示例

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestPrintByOrder {

    static Object lock = new Object();
    static boolean if2Printed = false;

    public static void main(String[] args){

        Thread thread1 = new Thread(() -> {

            synchronized (lock){
                while(!if2Printed){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                log.debug("1+++++++");

            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                log.debug("2+++++++");
                if2Printed = true;
                lock.notify();
            }
        });

        thread1.start();
        thread2.start();

    }

}

打印结果

 park、unpark版本代码示例  这个比 上面那个版本简化了 不针对锁操作 而是针对线程,省去了 锁对象 

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestPrintByOrder {

    static Object lock = new Object();
    static boolean if2Printed = false;

    public static void main(String[] args){

        Thread thread1 = new Thread(() -> {

                while(!if2Printed){
                    LockSupport.park();
                }

                log.debug("1+++++++");

        });

        Thread thread2 = new Thread(() -> {
                log.debug("2+++++++");
                if2Printed = true;
                LockSupport.unpark(thread1);
        });

        thread1.start();
        thread2.start();

    }

}

 打印结果

7.12.9 设计模式 交替运行

现在有这样一道题 该咋做呢?整理思路 就是 最先先运行的线程 确定 每次某线程执行完后 会接着 运行下一线程 直到 15 次

 wait ,notify版本

package com.robert.concurrent.test;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestPrintByAlternate {

    private static Thread thread1 = null;
    private static Thread thread2 = null;
    private static Thread thread3 = null;

    public static void main(String[] args){

        WaitNotidfy printUtil = new WaitNotidfy(5);
        printUtil.setFlag(1);

         thread1 = new Thread(() -> printUtil.print("a",1,2));

         thread2 = new Thread(() -> printUtil.print("b",2,3));

         thread3 = new Thread(() -> printUtil.print("c", 3,1));

        thread1.start();
        thread2.start();
        thread3.start();
    }

}

@Slf4j(topic = "printDemo")
@Data
class  WaitNotidfy{

    private  int loopnumber;
    private int flag;

    public void print(String  printContent,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("thread:"+Thread.currentThread().getName()+",loopnumber:"+i+",printContent:"+printContent);
                flag = nextFlag;
                this.notifyAll();
            }
        }

    }

    public WaitNotidfy(int loopnumber) {
        this.loopnumber = loopnumber;
    }

}

await ,signal版

package com.robert.concurrent.test;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.test1")
public class TestPrintByAlternate {

    private static Thread thread1 = null;
    private static Thread thread2 = null;
    private static Thread thread3 = null;


    public static void main(String[] args) throws InterruptedException {

        AwaitSignal printUtil = new AwaitSignal(5);
        Condition aCondition = printUtil.newCondition();
        Condition bCondition = printUtil.newCondition();
        Condition cCondition = printUtil.newCondition();

         thread1 = new Thread(() -> printUtil.print("a",aCondition,bCondition));

         thread2 = new Thread(() -> printUtil.print("b",bCondition,cCondition));

         thread3 = new Thread(() -> printUtil.print("c", cCondition,aCondition));

        thread1.start();
        thread2.start();
        thread3.start();

        TimeUnit.SECONDS.sleep(1);
        printUtil.lock();
        aCondition.signal();
        printUtil.unlock();
    }

}

@Slf4j(topic = "printDemo")
@Data
class  AwaitSignal extends ReentrantLock {

    private  int loopnumber;

    public void print(String  printContent,Condition currentCondition,Condition nextCondition){

        for (int i = 0; i < loopnumber; i++) {
            this.lock();
                    try {
                        currentCondition.await();
                        System.out.println("thread:"+Thread.currentThread().getName()+",loopnumber:"+i+",printContent:"+printContent);
                        nextCondition.signal();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        this.unlock();
            }
        }

    }

    public AwaitSignal(int loopnumber) {
        this.loopnumber = loopnumber;
    }

}

park,unpark版 

package com.robert.concurrent.test;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.test1")
public class TestPrintByAlternate {

    private static Thread thread1 = null;
    private static Thread thread2 = null;
    private static Thread thread3 = null;


    public static void main(String[] args) {

        ParkUnpark printUtil = new ParkUnpark(5);

         thread1 = new Thread(() -> printUtil.print("a",thread2));

         thread2 = new Thread(() -> printUtil.print("b",thread3));

         thread3 = new Thread(() -> printUtil.print("c",thread1));

        thread1.start();
        thread2.start();
        thread3.start();

        LockSupport.unpark(thread1);

    }

}

@Slf4j(topic = "printDemo")
@Data
class  ParkUnpark{

    private  int loopnumber;

    public void print(String  printContent,Thread nextThread) {

        for (int i = 0; i < loopnumber; i++) {
            LockSupport.park();
            System.out.println("thread:" + Thread.currentThread().getName() + ",loopnumber:" + i + ",printContent:" + printContent);
            LockSupport.unpark(nextThread);
        }
    }

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

打印结果都是如下

八、共享模型之内存

8.1 内存模型

JMM java内存模型 里面包含 主存 与工作内存 主存 是存的共享的变量 而工作内存存的是线程独有的变量  原子性在我们之前说的 syncronized或是 ReentrantLock就解决了  这一大节主要说另外两个特性 

8.2 可见性问题

如下代码示例可以看到 

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.test1")
public class TestVolatile {

    static boolean printFlag = true;

    public static void main(String[] args) throws InterruptedException {


        Thread thread1 = new Thread(() -> {

            while(printFlag){

            }

        });
        thread1.start();

        TimeUnit.NANOSECONDS.sleep(1);
        printFlag = false;

    }

}

运行结果如下 程序一直在运行 并没有停止 说明了虽然主线程修改了 printFlag但是thread1线程看不见

 为啥一个线程修改了共享变量对另一个线程不可见呢?原因如下三张图

 

8.3可见性问题解决

可以看到 我在共享变量上加了 volatile 关键字后  可见性问题解决,程序正常运行结束,除此之外还可以在使用共享变量的地方都加上 syncronzed 锁起来 这样会强制同步主存中的值到当前工作内存

8.4 设计模式

8.4.1 两阶段终止模式 

这个不多说 就是 8.2 的示例代码中 两个线程如果共享一个变量 并且 双方要实时同步另一方修改后的值 那么记得要加 volatile

 8.4.2 bulking (犹豫)模式

 这个其实也很简单 单例模式中可以见到 就是提前判断 下实例是否存在 或者 相关 条件是否满足 否则 直接返回实例 或是return 结束方法 如下代码 判断部分记得也要加锁 不然 并发情况下 两个线程同时 完成if 判断 导致 if没有拦住

  

8.5 指令重排

8.5.1 简介

简单说下 cpu 有时候在 不影响单核cpu情况下程序运行结果时 进行指令的重排 以提高cpu的性能,但是这样会导致 多核cpu,多线程时的一些程序运行的错误 

 8.5.2 为什么会出现指令重排

简单来说就是cpu为了提升性能的优化,就好比 我洗衣服晾衣服是一条指令,我煮饭盛饭是一条指令 那么我可以 把衣服放在洗衣机洗衣的同时 去煮饭 然后 晾完衣服又去 盛饭 这样 如图二就实现了一个流水线的类似效果 

 

 

  

8.5.3 指令重排序的验证

如下代码,两个线程分别调用 actor1与 actor2方法 打印的r1最终结果可能有三种 4,1,0

4,1可以理解,分别是 ready为true 或是false时的打印结果 ,而0则是 num = 2 与 ready = true两处指令呼唤后重排序的结果 当重排序后 执行完 ready =  true 时 被线程1抢回cpu 执行权 此时 r1就变成 0+0最终结果为0了

 

 为了验证这一可能性 使用压测工具压测后 发现果然有如下问题 五千多万次中有一千多次重排序导致程序结果错误

 

8.5.4 禁用 指令重排序

在变量前面加上volatile修饰即可 ,为啥在ready上加 而不加num呢?后面volatile原理会讲,利用了一个写屏障 使得其前面的代码不会被重排序

8.5.5 volatile保证 可见性 有序性的原理

 可见性保证

 

 有序性保证

 

 

可能下面这段代码大家都熟悉 单例模式的懒汉模式 ,双重判断的保证 使得效率的提升,第一重判断保证只有 第一次instance为空时 才会走后面的尝试获取锁的操作,第二次 判断多线程时保证了单例 

 但是现在还有个隐藏的问题,看到如下字节码指令可以看到 new 对象其实包含了 黄线上面 两行指令是 新建实例 然后拷贝一份 黄线部分是 调用构造方法 然后 赋值给 INSTANCE变量

 

 

 

 

 当加上 volatile关键字后 由于 volatile会给被修饰的变量 写的时候 最后一条指令后加上写屏障,读的时候最前面一条指令加上读屏障 所以保证了 指令21  不会跑到 指令 24后面来 ,所以就不会导致虽然 INSTANCE引用不为null但是 没调用构造方法就被t2线程使用这样一个问题

 出现如下这种情况时,由于 t1先执行构造方法但是还没给INSTANCE赋值,t2线程就调用 判断 

INSTANCE是否为null,这个时候明显为null所以它被 syncronized阻塞 t1释放锁后此时 INSTANCE

 不为null了(volatile 保证了线程该行与该行之前的对工作内存的变量操作都会同步主t2线程获取锁后判断 INSTANCE不为null所以直接返回INSTANCE

8.6 happens-before 

就是对有序性及可见性的一些描述 有下面七个,看看就好

 

 

 

 

 

8.7 练习题 考验本章掌握程度

相信有过一定学习经验的你都知道,知识只学不去输出是很难长久记忆的 练了做了题记忆才会更深刻,现在开始 每道题我将会把自己的答案写在上面 你可以做完后验证

8.7.1 以下实现是否有问题

多线程情况下是有问题的,可以看到这段代码的目的是使用 initialized变量来控制 调用doInit()方法使其只调用一次

但是 当线程t1首次进入t1然后 走到init方法内时 此时线程t2也运行到 init方法中 此时因为initialized = true这段代码还没执行 所以 t2线程也会再次 调用 doInit()方法  解决方式就是 加锁

 8.7.2 看图分析以下五个问题

问题1:为啥加final,这样是为了保证单例 可以看出这是饿汉模式

问题2:实现了序列化接口,还要做啥来防止反序列化,图中答案已经给出 添加一个 readResolve方法代码如图 因为反序列时判断当前有 readResolve方法回直接返回 此方法的返回值

问题3:设置私有是为了 除了本类别的地方不能new出该类实例,至于防止反射创建实例 还做不到

因为 反射中有个 setAccessible(true) 可以强行访问私有方法

问题4:能够保证,因为静态成员变量的初始化是在 某类被第一次使用时类加载时做的,这是在jvm层面做的,保证了线程安全

问题5:这就涉及到面向对象了,隐藏细节,提升可扩展性

 8.7.3 看图分析以下六个问题 

问题1:如何限制单例,可以从图二看出,字节码层面 INSTANCE其实就是该类的静态成员变量

问题2:没有并发问题 静态成员变量在类加载时初始化 由jvm操作,保证了安全性

问题3:底层代码保证了

问题4:底层代码保证了

问题5:饿汉式,因为它字节码可以看出是静态成员变量,而静态成员变量类加载时就被初始化创建 

问题6:枚举类也可添加构造方法 

 

 8.7.4 分析这里的线程安全,并说明有啥问题

syncronized把整个静态方法都包住了,显然没有线程安全问题

缺点:因为同步块内代码多,每次创建实例的时候都要加锁 影响性能

 

  

8.7.4 看图回答下面三个问题

问题1:为了保证其有序性与可见性

问题2:这样就没有了实现3的缺点 不至于每次创建实例都要加锁

问题3:这里前面说了,不加的化多线程环境下 会有问题 可能创建多个实例对象

8.7.5 看图回答下面两个问题

问题1:懒汉式,静态成员只会在所属类被使用时才加载

问题2:没有线程安全问题 前面说了,静态成员的加载由jvm层面实现保证了线程安全问题

九、无锁并发(乐观锁-非阻塞)

9.1前言

相比于前面的有锁并发,本节是采用了一个叫乐观锁的东东,使得在无锁条件下依然能保证线程安全

 内容概览

9.2 CAS

9.2.1代码示例

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j(topic = "c.testCAS")
public class TestCAS {

    public static void main(String[] args){

        List<Thread> threadList = new ArrayList<>();
        AccountCAS accountCAS = new AccountCAS();
        accountCAS.setBalance(1000);

        for (int i = 0; i < 100; i++) {
            threadList.add(new Thread(() -> accountCAS.withdraw(10)));
        }

        threadList.forEach(Thread::start);

        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        log.debug("accountCAS 账户余额为:{}",accountCAS.getBalance().get());
    }

}

@Slf4j
class AccountCAS implements Account{

    private volatile AtomicInteger balance;

    @Override
    public AtomicInteger getBalance() {
        return this.balance;
    }

    @Override
    public void setBalance(int balance) {
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public void withdraw(int amount) {
        if(balance.get() - amount < 0){
            throw  new RuntimeException("余额不足 无法支付");
        }

        int prev = balance.get();
        int next = prev - amount;

        while(true){
            if(balance.compareAndSet(prev,next)){
                break;
            }
        }
    }
}

interface Account{

    AtomicInteger getBalance();

    void setBalance(int balance);

    void withdraw(int amount);

}

可以看出如下,得到了正确的结果,余额为1000 100个并发取钱,每次取10块,余额为0

 9.2.2 CAS工作方式

如图 就是就是不断比较然后尝试设置值 如果修改值时发现 值与 线程最开始最开始获取到的不一致时 说明中间被其它线程修改了 所以修改失败 然后继续下一次while循环

  9.2.3 CAS与volatile

9.2.4 CAS效率分析

因为 无锁情况下 线程保持运行,需要cpu核的支持 所以线程数小于 cpu核数的情况下 用 CAS能提升效率,当线程数远大于  CPU核数时 这个时候起始 CAS也就需要线程上下文切换 比起有锁情况下 也就不能提升效率了

 9.2.5 CAS特点

 9.3  使用CAS实现的atomic子包工具类

9.3.1 原子整数 

以下三种类型 就不多介绍了,方法大同小异

 把上一节写的代码从图一改成图二是一样的效果 这些工具类的方法都保证了原子性

 

还一个updateAndGet 这个不同的是它可以 传一个像 图二类型的接口 以后看到这个就要知道这是可以传 lambda表达式的意思 跟 compareAndSet底层原理基本一样 不同的是 update的值变成了一个方法

 

 底层实现利用的 compareAndSet,与一个接口 很简单 

9.3.2 原子引用

因为我们的共享变量不止是 基本类型,还有引用类型所以才需要原子引用类型

简单介绍下下面三种 原子引用类型的使用场景 第一种 普通的引用类型

第二种,第三种 可以防止 ABA问题 第二种是记录 true false,第三种是记录版本号 

9.3.3 AtomicReference

AtomicReference代码示例
package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j(topic = "c.testCAS")
public class TestCAS {

    public static void main(String[] args){

        List<Thread> threadList = new ArrayList<>();
        AccountCAS accountCAS = new AccountCAS();
        accountCAS.setBalance(new BigDecimal(1000));

        for (int i = 0; i < 100; i++) {
            threadList.add(new Thread(() -> accountCAS.withdraw(new BigDecimal(10))));
        }

        threadList.forEach(Thread::start);

        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        log.debug("accountCAS 账户余额为:{}",accountCAS.getBalance());
    }

}

@Slf4j
class AccountCAS implements Account{

    private volatile AtomicReference<BigDecimal> balance;

    @Override
    public BigDecimal getBalance() {
        return this.balance.get();
    }

    @Override
    public void setBalance(BigDecimal balance) {
        this.balance = new AtomicReference(balance);
    }

    @Override
    public void withdraw(BigDecimal amount) {


        while(true){
            BigDecimal pre = balance.get();
            BigDecimal after = pre.subtract(amount);
            if(after.compareTo(new BigDecimal(0))  < 0){
                throw  new RuntimeException("余额不足 无法支付");
            }

           if(balance.compareAndSet(pre,after)){
                break;
           }

        }

    }
}

interface Account{

    BigDecimal getBalance();

    void setBalance(BigDecimal balance);

    void withdraw(BigDecimal amount);

}

9.3.4 AtomicStampedReference

 代码示例 如下 主线程修改时 发现当前版本与 最新版本不同,所以修改失败

 

9.3.5 AtomicMarkableReference

用法如图 与前面的传前后版本号不同,这里传 true ,false即可 

9.3.6 原子数组

示例代码如下图二,图一为方法参数介绍

如下代码做的啥事呢?

第一个参数:首先通过参数获取到一个数组,

第二个参数:然后获取到 这个数组长度为10  

第三个参数:然后 创建10个线程 每个线程内循环10000次  每次都令某个元素+1 该元素取模获得

没有指令交错情况下最终结果应该都是10000

第四个参数:打印数组

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j(topic = "c.testCAS")
public class TestCAS {

    public static void main(String[] args){

        demo(
                () -> new int[10],
                (array) -> array.length,
                (array,index) -> array[index]++,
                (array) -> log.debug(Arrays.toString(array))
            );

        demo(
                () -> new AtomicIntegerArray(10),
                (array) -> array.length(),
                (array,index) -> array.getAndIncrement(index),
                array -> log.debug("{}",array)
        );
    }

    private static <T> void demo(
            Supplier<T> arraySuplier,
            Function<T,Integer> lengthFun,
            BiConsumer<T,Integer> putConsumer,
            Consumer<T> printConsumer
            ) {

        List<Thread> threadList = new ArrayList<>();
        T array = arraySuplier.get();
        Integer length = lengthFun.apply(array);
        for (int i = 0; i < length; i++) {
            threadList.add(new Thread(() ->{
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array,j%length);
                }
            }));
        }

        threadList.forEach(Thread::start);
        threadList.forEach((t) ->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        printConsumer.accept(array);
    }

}

可以看到,原子数组能保证原子性  

 9.3.7 原子更新器

对某个对象的某个属性,做原子更新操作就可以用它 ,用法示例如下

package com.robert.concurrent.test;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

@Slf4j(topic = "c.testCAS")
public class TestCAS {

    public static void main(String[] args){

        Student student = new Student();

        AtomicReferenceFieldUpdater atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
        atomicReferenceFieldUpdater.compareAndSet(student,null, "小明");
        System.out.println(student.getName());

    }

}

@Data
class Student {
    volatile String name;
}

9.3.8 原子累加器 LongAdder

这个是1.8后替代 AtomicInteger的add方法的 性能比它们好 可以看到下面代码,性能提升了4,5倍

 性能提升的原因

9.3.9  LongAdder原理 与源码 分析

简介: CAS可以做锁 但是一般用在底层代码中,所以开发人员一般还是不要轻易使用 用别人封装好的即可,关于LongAdder的源码 之前说过 它是由多个线程 在一个Cell数组中分别进行CAS累加 最后再把结果汇总起来这样一个提升效率的方式 

9.3.9.1  LongAdder原理

上面简单说了下原理,现在看下Cell类的代码 的确是 CAS , 尤为重要的是上面这样一个注解

Contended:竞争

 大致看下下面几张图 说的主要是这样一个事: 由于从CPU中读取数据并不快,后面演化出 多级缓存然后每个线程 所处的每个cpu核心都对应这样一套缓存 先从内存中拷贝多份数据到各cpu核心对应的缓存中 读取的时候就从缓存中读取,而缓存中以缓存行为单位存数据 一个缓存行128byte,

当一个 缓存行的数据被修改后会同步到内存  同时另外一个cpu核心对应的这个缓存行也会失效效,然后重新从内存中拷贝一份最新数据,但是我们这里 long型的数据 之前我们学锁的时候知道

对象头(markword+类型指针)+value存储 总共是 16+8 = 24个字节 那么 一个缓存行就会被存放多个CELL,然后一个cell被修改,其它cell因为处在同一个缓存行所以 也会 同步到内存 并且其它核心的对应缓存行 也会失效 这样极大影响了性能,而现在这个注解就是 看最后一张图 前后各增加128字节 使得 不同cell占用不同缓存行 就不会造成刚刚说的其它缓存行失效的问题

 

 

 

9.3.9.2  LongAdder源码(待补充...)

最终是统计结果,前面的源码待补充

9.4 UNSAFE 对象

这里UNSAFE:不安全的意思,不是说它的操作不安全,而是说 它都是针对内存啥的操作的,开发人员不要轻易去碰 可能不安全

9.4.1 使用unsafe对象做 对象某属性的CAS

使用Unsafe对象实现对象的某个属性的CAS 这里需要获取 属性对应对象的内存偏移地址,代码如下

package com.robert.concurrent.test;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

@Slf4j(topic = "c.TestUnsafe")
public class TestUnsafe {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        Field theUnsafe =  Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe)theUnsafe.get(null);

        Field idField = Boy.class.getDeclaredField("id");
        Field nameField = Boy.class.getDeclaredField("name");

        long idFieldOffset = unsafe.objectFieldOffset(idField);
        long nameFieldOffset = unsafe.objectFieldOffset(nameField);

        Boy boy = new Boy();

        unsafe.compareAndSwapInt(boy,idFieldOffset,0,1);
        unsafe.compareAndSwapObject(boy,nameFieldOffset,null,"小明");

        log.debug("{}", boy);


    }

}

@Data
class Boy {

    private int id;
    private String name;
}

9.4.2 使用unsafe对象实现一个AtomicInteger类

因为这里基本跟前面一样的代码 无非是 成员属性为int型 然后 必须要加上 while(true)的判断来做CAS

9.5 可变对象的 线程安全问题

9.5.1 SimpleDateFormat 线程不安全

可以看到多个线程同时调用parse方法时会报错

9.5.2jdk1.8 线程安全的日期格式转换类的引进 ——DateTimeFormatter

9.6 不可变对象怎样设计的

如下可以看到 成员变量都是私有的并且不提供set方法,然后引用类型的成员变量 会在 首次 创建时 value指向 复制出的一份内存拷贝  后面 用户修改 就修改不到 称作保护性拷贝

 

 

 

9.7 final原理

主要就是底层加了内存写屏障 使得多线程访问时就不会读到不同的值

 当 int 类型 被声明为final时 短整型 -128-127会被直接创建出来放到栈内存中 如果不是这个范围就到常量池中找  如果不是final类型就需要从堆中获取 效率就会降低

从常量池中获取

 从共享内存中获取

 9.8 手写简易版数据库连接池

代码如下 为啥 要使用 wait 与 notify呢?直接就用 CAS不好么?不行 因为 正常情况下 获得连接后的操作时间都不断 到 释放链接之前 CAS要一直 循环 会影响性能

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j(topic = "c.PoolTest")
public class PoolTest{

    public static void main(String[] args) {

        Pool pool = new Pool(3);

        for (int i = 0; i < 5; i++) {
                new Thread(() ->{
                    try {
                        log.debug("获取连接");
                        DBConnection connection = pool.getConnection();

                        log.debug("业务处理中");
                        TimeUnit.SECONDS.sleep(1);

                        log.debug("释放连接");
                        pool.releaseConnection(connection);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                },"线程"+(i+1)).start();
        }

    }

}

@Slf4j
class Pool {

    private int poolSize;
    private DBConnection[] dbConnections;
    private AtomicInteger[] connectionStates ;

    public Pool(int poolSize) {
        this.poolSize = poolSize;
        this.dbConnections = new DBConnection[poolSize];
        for (int i = 0; i < poolSize; i++) {
            this.dbConnections[i] = new DBConnection();
        }
        this.connectionStates = new AtomicInteger[poolSize];
        for (int i = 0; i < poolSize; i++) {
            this.connectionStates[i] = new AtomicInteger(0);
        }
    }

    DBConnection getConnection() throws InterruptedException {

        while(true){

            for (int i = 0; i < connectionStates.length; i++) {
                if(connectionStates[i].compareAndSet(0,1)){
                    return dbConnections[i];
                }
            }
            synchronized (this){
                log.debug("等待中");
                this.wait();
        }
        }

    }

    void releaseConnection(DBConnection dbConnection){
        for (int i = 0; i < dbConnections.length; i++) {
            if(dbConnection == dbConnections[i]){

                if(connectionStates[i].compareAndSet(1,0)){
                    synchronized (this){
                        this.notifyAll();
                    }
                }
            }
        }
    }

}

class DBConnection{

}

十、线程池

10.1前言

来源或是作用:

线程:系统的一种资源,每创建一个线程,需要给其分配一块栈内存  如果高并发情况下,不可能为每个线程都分配一块栈内存,因为那样会占用相当大的内存,甚至可能出现OOM,而且线程也不是创建的越多越好,超过CPU核数就这么多,获取不到CPU时间片的线程都会陷入阻塞,就会引起上下文切换(保存当前线程状态然后切换成其它线程,后面又要切换回来恢复当前线程状态)的问题,切换越频繁反而会导致系统性能降低,而是充分利用 已有线程资源

线程池的作用:让线程重复被利用,这样可以减少线程的数量,也可以避免频繁的上下文切换,在体验JDK的线程池前,让我们自定义一个吧

10.2 自定义线程池

 主要是以下设计 有一个线程池 专门存取线程,然后有个阻塞队列,用来存放任务 因为高并发情况下任务量很大,不可能同时执行,所以没拿到线程资源执行的那些,就在阻塞队列乖乖排队

 代码如下 肝了几小时 (太菜了)

经验总结:while 循环中等待 不要 把外面解除while循环等待的代码放到 while循环里面,一不小心卡了很久  

package com.robert.concurrent.test;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


@Slf4j(topic = "ThreadPoolTest")
public class ThreadPoolTest {

    public static void main(String[] args) throws InterruptedException {

        ThreadPool threadPool = new ThreadPool(2, 1, TimeUnit.SECONDS, 3,
                (blockingQueue, task) -> {
//                    // 拒绝策略1:一直等 队列空了就入队
//                    blockingQueue.put(task);


//                    // 拒绝策略2:交由当前线程执行
//                    task.run();

//                    //拒绝策略3 直接拒绝
//                    throw new RuntimeException("队列已满,新任务无法执行");

                    //拒绝策略4 超时等待
                    blockingQueue.putWithTimeout(task, 2, TimeUnit.SECONDS);

                });

        for (int i = 0; i < 8; i++) {
            int k = i;
            threadPool.execute(() -> {
                try {
                    System.out.println("当前线程" + Thread.currentThread().getName() + "任务" + k + "执行中");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }


    }

}

@Slf4j
class ThreadPool {

    private int coreSize;

    HashSet<MyWorker> workers;

    private int timeout;

    private TimeUnit timeUnit;

    private MyBlockingQueue<Runnable> blockingQueue;

    private int blockingQueueSize;

    private MyRejectPolicy rejectPolicy;

    public ThreadPool(int coreSize, int timeout, TimeUnit timeUnit, int blockingQueueSize, MyRejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        workers = new HashSet<>(coreSize);
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.blockingQueueSize = blockingQueueSize;
        blockingQueue = new MyBlockingQueue(blockingQueueSize);
        this.rejectPolicy = rejectPolicy;
    }

    public void execute(Runnable task) {

        synchronized (workers) {
            if (workers.size() < coreSize) {
                MyWorker myWorker = new MyWorker(task);
                workers.add(myWorker);
                System.out.println("当前线程" + Thread.currentThread().getName() + "工作线程+1 并运行");
                myWorker.start();

            } else {
                blockingQueue.put(task);
            }
        }
    }

    class MyWorker extends Thread {

        private Runnable task;

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

        @Override
        public void run() {

            while (task != null || (task = blockingQueue.takeWithTimeout(timeout,timeUnit)) != null) {
                try {
                    task.run();
                } finally {
                    task = null;
                }
            }

            synchronized (workers) {
                workers.remove(this);
                System.out.println("线程 "+this.getName()+" 超时时间内未工作已已从线程池中移除");
            }
        }
    }

    @FunctionalInterface
    interface MyRejectPolicy<T> {

        void reject(ThreadPool.MyBlockingQueue blockingQueue, T task);

    }

    class MyBlockingQueue<T> {

        //队列容量
        private int queueSize;

        //存放任务的 队列
        private Deque<T> taskQueue;

        //锁
        private ReentrantLock lock = new ReentrantLock();

        //队列空时条件变量
        private Condition emptyCondition = lock.newCondition();

        //队列满时条件变量
        private Condition fullCondition = lock.newCondition();

        public MyBlockingQueue(int queueSize) {
            this.queueSize = queueSize;
            taskQueue = new ArrayDeque<>(queueSize);
        }


        public T takeWithTimeout(int timeout, TimeUnit timeUnit) {

            try {
                long nanosTimeout = timeUnit.toNanos(timeout);
                lock.lock();
                while (taskQueue.isEmpty()) {

                    if (nanosTimeout <= 0) {
                        return null;
                    }

                    System.out.println("当前线程" + Thread.currentThread().getName() + "阻塞队列为空 没取到任务 等待中 ");
                    // 被打断 后依然等待剩余时间
                    nanosTimeout = emptyCondition.awaitNanos(nanosTimeout);
                    System.out.println("当前线程" + Thread.currentThread().getName() + "等待太久没取到任务 已超时");
                    return null;
                }
                T t = taskQueue.removeFirst();
                fullCondition.signalAll();
                return t;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return null;
        }


        public T take() {
            lock.lock();
            try {
                while (taskQueue.isEmpty()) {
                    System.out.println("当前线程" + Thread.currentThread().getName() + "阻塞队列为空 没取到任务 等待中 ");
                    emptyCondition.await();
                }
                T t = taskQueue.removeFirst();
                System.out.println("当前线程" + Thread.currentThread().getName() + "从队列取到任务 :" + t);
                fullCondition.signalAll();
                return t;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return null;
        }

        public boolean putWithTimeout(T task, int timeout, TimeUnit timeUnit) {

            try {
                long nanosTimeout = timeUnit.toNanos(timeout);
                lock.lock();
                while (taskQueue.size() == queueSize) {

                    if (nanosTimeout <= 0) {
                        return false;
                    }
                    System.out.println("当前线程" + Thread.currentThread().getName() + "阻塞队列满了 ,没放进去 ");
                    nanosTimeout = fullCondition.awaitNanos(nanosTimeout);
                }
                taskQueue.addLast(task);
                emptyCondition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return true;
        }

        public void put(T task) {

            try {
                lock.lock();
                while (taskQueue.size() == queueSize) {
                    //如果阻塞队列已满 执行拒绝策略
                    rejectPolicy.reject(blockingQueue, task);
                }
                taskQueue.addLast(task);
                System.out.println("当前线程" + Thread.currentThread().getName() + "阻塞队列任务+1");
                emptyCondition.signal();
            } finally {
                lock.unlock();
            }
        }

        public int size() {
            try {
                lock.lock();
                return taskQueue.size();
            } finally {
                lock.unlock();
            }

        }
    }


}

10.3 JDK中的线程池 ——ThreadPoolExecutor

10.3.1线程池状态

10.3.2 构造方法

工作方法

 

10.4  Executors的工厂方法

10.4.1 newFixedThreadPool

 示例代码如下

 10.4.2 newCachedThreadPool

  示例代码如下

 可以看到 put操作会阻塞住直到有take操作

10.4.3 newSingledThreadPool

可以像如下覆盖默认的线程工厂 

10.5 ExecutorService 的常用方法

10.5.1 submit

它带返回值的  execute不带返回值

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit{

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Future<String> stringFuture = executorService.submit(() -> {
            log.debug("我是嫩爹 。。。");
            Thread.sleep(1000);
            return "ok";
        });

        String result = stringFuture.get();
        log.debug("运行结果:{}", result);

    }

}

10.5.2 invokeAll

运行多个线程 返回多个结果

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit{

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(2);

        List<Future<Object>> futures = executorService.invokeAll(Arrays.asList(
                () -> {
                    log.debug("我是嫩爹1 。。。");
                    Thread.sleep(500);
                    return "ok";
                },
                () -> {
                    log.debug("我是嫩爹2 。。。");
                    Thread.sleep(1000);
                    return "ok";
                },
                () -> {
                    log.debug("我是嫩爹3 。。。");
                    Thread.sleep(2000);
                    return "ok";
                }
        ));

        futures.forEach((result) -> {
            try {
                log.debug("运行结果:{}", result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });


    }

}

10.5.3 invokeAny

当线程数大于1时,返回最快结束的那个线程的运行结果

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit{

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        String result = executorService.invokeAny(Arrays.asList(
                () -> {
                    log.debug("我是嫩爹1 。。。");
                    Thread.sleep(1500);
                    log.debug("我是嫩爹1 。。。end");
                    return "ok1";
                },
                () -> {
                    log.debug("我是嫩爹2 。。。");
                    Thread.sleep(1000);
                    log.debug("我是嫩爹2 。。。end");
                    return "ok2";
                },
                () -> {
                    log.debug("我是嫩爹3 。。。");
                    Thread.sleep(2000);
                    log.debug("我是嫩爹3 。。。end");
                    return "ok3";
                }
        ));

        log.debug("运行结果:{}", result);


    }

}

当线程数等于1时,返回第一个线程 的运行结果

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit{

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService executorService = Executors.newSingleThreadExecutor();

        String result = executorService.invokeAny(Arrays.asList(
                () -> {
                    log.debug("我是嫩爹1 。。。");
                    Thread.sleep(1500);
                    log.debug("我是嫩爹1 。。。end");
                    return "ok1";
                },
                () -> {
                    log.debug("我是嫩爹2 。。。");
                    Thread.sleep(1000);
                    log.debug("我是嫩爹2 。。。end");
                    return "ok2";
                },
                () -> {
                    log.debug("我是嫩爹3 。。。");
                    Thread.sleep(2000);
                    log.debug("我是嫩爹3 。。。end");
                    return "ok3";
                }
        ));

        log.debug("运行结果:{}", result);


    }

}

10.5.4 shutDown 与 shutDownNow

shutDown

示例代码如下

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit{

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Future<Object>> futures = executorService.invokeAll(Arrays.asList(
                () -> {
                    log.debug("我是嫩爹1 。。。");
                    Thread.sleep(1500);
                    log.debug("我是嫩爹1 。。。end");
                    return "ok1";
                },
                () -> {
                    log.debug("我是嫩爹2 。。。");
                    Thread.sleep(1000);
                    log.debug("我是嫩爹2 。。。end");
                    return "ok2";
                },
                () -> {
                    log.debug("我是嫩爹3 。。。");
                    Thread.sleep(2000);
                    log.debug("我是嫩爹3 。。。end");
                    return "ok3";
                }
        ));

        executorService.shutdown();

        List<Future<Object>> futures2 = executorService.invokeAll(Arrays.asList(() -> {
            log.debug("我是嫩爹4 。。。");
            Thread.sleep(1500);
            log.debug("我是嫩爹4 。。。end");
            return "ok4";
        }));


    }

}

 shutDownNow

可以看到 shutDownNow打断了所有正在该线程池中执行的线程 

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        Future<String> submit = executorService.submit(() -> {
            log.debug("我是嫩爹1 。。。");
            Thread.sleep(1500);
            log.debug("我是嫩爹1 。。。end");
            return "ok1";
        });
        Future<String> submit2 = executorService.submit(() -> {
            log.debug("我是嫩爹2 。。。");
            Thread.sleep(1000);
            log.debug("我是嫩爹2 。。。end");
            return "ok2";
        });
        Future<String> submit3 = executorService.submit(() -> {
            log.debug("我是嫩爹3 。。。");
            Thread.sleep(2000);
            log.debug("我是嫩爹3 。。。end");
            return "ok3";
        });


        List<Runnable> runnables = executorService.shutdownNow();

        List<Future<Object>> futures2 = executorService.invokeAll(Arrays.asList(() -> {
            log.debug("我是嫩爹4 。。。");
            Thread.sleep(1500);
            log.debug("我是嫩爹4 。。。end");
            return "ok4";
        }));


    }

}

invoke相关方法执行的线程不会被shutDownNow打断

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit{

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Future<Object>> futures = executorService.invokeAll(Arrays.asList(
                () -> {
                    log.debug("我是嫩爹1 。。。");
                    Thread.sleep(1500);
                    log.debug("我是嫩爹1 。。。end");
                    return "ok1";
                },
                () -> {
                    log.debug("我是嫩爹2 。。。");
                    Thread.sleep(1000);
                    log.debug("我是嫩爹2 。。。end");
                    return "ok2";
                },
                () -> {
                    log.debug("我是嫩爹3 。。。");
                    Thread.sleep(2000);
                    log.debug("我是嫩爹3 。。。end");
                    return "ok3";
                }
        ));

        List<Runnable> runnables = executorService.shutdownNow();

        log.debug("未执行玩的任务数"+runnables.size());

        List<Future<Object>> futures2 = executorService.invokeAll(Arrays.asList(() -> {
            log.debug("我是嫩爹4 。。。");
            Thread.sleep(1500);
            log.debug("我是嫩爹4 。。。end");
            return "ok4";
        }));


    }

}

10.5.5 其它方法

10.6 定时任务 之timer 与 ScheduledExecutorService

10.6.1 timer

timer 不建议使用 起不到定时的效果 因为他只会在一个线程中串行运行,如下,并且前一个任务如果因为异常而停止运行,后面线程也会运行不了

10.6.2 ScheduledExecutorService

可以看到 只要我线程池核心线程数量大于 定时任务数 那么他们就是并行执行 不会相互影响,并且执行中的异常会被catch起来 后面会说怎么处理 

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

        ScheduledFuture<String> schedule1 = scheduledExecutorService.schedule(() -> {
            log.debug("我是嫩爹1 。。。");
            Thread.sleep(1000);
            log.debug("我是嫩爹1 。。。end");
            int i = 10 / 0;
            return "ok1";
        }, 1, TimeUnit.SECONDS);
        final ScheduledFuture<String> schedule2 = scheduledExecutorService.schedule(() -> {
            log.debug("我是嫩爹2 。。。");
            Thread.sleep(1500);
            log.debug("我是嫩爹2 。。。end");
            return "ok2";
        }, 1, TimeUnit.SECONDS);



    }

}

10.6 正确处理线程池中异常

除了正常的使用catch 处理外 在线程池中的异常,我们还可以用callable封装任务 然后后面get的时候就会抛异常 如下代码示例

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

        ScheduledFuture<String> schedule1 = scheduledExecutorService.schedule(() -> {
            log.debug("我是嫩爹1 。。。");
            Thread.sleep(1000);
            log.debug("我是嫩爹1 。。。end");
            int i = 10 / 0;
            return "ok1";
        }, 1, TimeUnit.SECONDS);
        final ScheduledFuture<String> schedule2 = scheduledExecutorService.schedule(() -> {
            log.debug("我是嫩爹2 。。。");
            Thread.sleep(1500);
            log.debug("我是嫩爹2 。。。end");
            return "ok2";
        }, 1, TimeUnit.SECONDS);

        log.debug("我是嫩爹1 。。。result", schedule1.get());
        log.debug("我是嫩爹2 。。。result", schedule2.get());

    }

}

10.7 定时任务

 如下代码为每周四 18:00:00执行  现在一般都用定时任务系统 整cron表达式,这个看看就好

10.8 tomcat —— 线程池

可以在server.xml中修改Executor或者Connector的配置

 

10.9 forkjoin 任务拆分优化

这玩意 jdk1.7出的  任务拆分的细节需要开发者自己实现 初学者自己弄还是可能不太会, jdk1.8

的stream流就是代码底层拆分优化的  看看就好,使用 这玩意需要强的算法能力支撑

 比如求等于5的正整数之和,我们一般用递归,使用forkjoin求递归任务结果方式如下,在compute方法内封装递归细节

 

十一、JUC

大纲 

11.1 aqs——自定义锁

11.1.1 介绍

它是我们前面学的 锁的实现的框架,我们可以用它来自己实现 一个符合自己业务场景的锁 

 

11.1.2 自定义锁

它是java层面的锁实现的父类,大部分方法都有默认实现,我们只需要自己写个子类 重写几个方法就能自定义锁了
比如现在我要设计一个不可重入的独占锁 示例代码如下

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Slf4j(topic = "c.TestSubmit")
public class TestSubmit {

    public static void main(String[] args){

        MyLock myLock = new MyLock();

        new Thread(() -> {

            try {
                myLock.lock();
                log.debug("我是嫩爹1 。。。end");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                myLock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            try {
                myLock.lock();
            log.debug("我是嫩爹2 。。。end");
            }finally {
                myLock.unlock();
            }
        },"t2").start();

    }

}

class MyLock implements Lock {

    class MySyncronizer extends AbstractQueuedSynchronizer{

        @Override
        protected boolean tryAcquire(int arg) {

            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }

            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setState(0);
            setExclusiveOwnerThread(null);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return true;
        }

        protected  Condition newCondition(){
            return new ConditionObject();
        }

    }

    MySyncronizer mySyncronizer = new MySyncronizer();


    @Override
        public void lock() {

        mySyncronizer.acquire(1);

    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        mySyncronizer.acquireSharedInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return mySyncronizer.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mySyncronizer.tryAcquireNanos(1,unit.toNanos(time));
    }

    @Override
    public void unlock() {
        mySyncronizer.release(1);
    }

    @Override
    public Condition newCondition() {
        return mySyncronizer.newCondition();
    }
}

11.2 reentrantlock 源码剖析

11.2.1 公平与非公平 锁的实现

可以看到 它默认构造方法是非公平锁

 再看它的lock方法 下面贴的代码稍微看看就好,后面带图解释

 

公平锁体现如下 

公平锁实现:Sync内部类中有一个双向链表,它是用来没抢到锁的线程排队的,head节点后面有个 值为空的node 来排队的线程放他后面,空值node后面有节点排队时 它的状态就被置为-1 当 锁被占有的线程释放时 就唤醒(unpark)空值node后的那个线程让他去抢占锁,若此时没有外部添加的线程跟他抢,或者有但是它抢到了,那它就被状态置为1 大致就这样 没抢到就通过不了判断,公平锁必须排队,不能插队

   

 

公平锁相关代码,会判断 等待队列

 11.2.3 可重入的实现

就是判断当前线程是否是占有锁的线程,是就把state++ 后面解锁就 state-- 图中序号请忽略

 11.2.3 可打断锁的实现

不可打断的如下 可以看到只会把 interrupted置为true

 可打断的如下 park状态下被打断 会抛打断异常

 11.2.4 条件变量的实现

11.3 ReentrantReadWriteLock

11.3.1简介

11.3.2 锁的 条件变量的支持 与 升降级

 读锁没释放前不能获取写锁 但是写锁释放前可以获取读锁 如下为代码示例

11.3.3 读写锁的原理 

 

 

 

 

 

 

 

 

11.3.4StamepLock

这个锁与之前提到的乐观锁的概念有点像  在读之前,可以尝试使用乐观锁的方式去读,若是版本

这里也就是stamp 若是没有变说明 这期间没有线程进行写操作 可以直接读 如果 stamp改变的话就不能直接读了,就要加上读锁,如果读锁没加上说明 写还在进行中 写锁没有释放 

 

 

 主要用到的锁代码如下

        StampedLock stampedLock = new StampedLock();
        long stamp = stampedLock.tryOptimisticRead();
        stampedLock.validate(stamp);

        long readStamp = stampedLock.readLock();
        stampedLock.unlockRead(readStamp);

        long writeStamp = stampedLock.writeLock();
        stampedLock.unlockWrite(writeStamp);

优势是提升无读写指令交错情况下读的效率  弊端挺明显的 他不支持以下两种 功能,可重入与条件变量

11.4 Semaphore

11.4.1使用示例

用来限制能同时访问某共享资源的线程数量,示例代码如下

package com.robert.concurrent.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.test12")
public class TestSemaphore {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 10; i++) {


            new Thread(() -> {
                try {
                    semaphore.acquire();
                    log.debug("运行中");
                    TimeUnit.SECONDS.sleep(1);
                    log.debug("运行结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }

}

 输出结果如下 可以看到同一时刻 共享代码块 只能有三个线程访问

11.4.2使用semaphore改进 自定义连接池

semaphore.acquire 代替 wait方法  semaphore.release 代替 notify方法  使得不用等待,直接拦截

package com.robert.concurrent.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j(topic = "c.PoolTest")
public class PoolTest{

    public static void main(String[] args) {

        Pool pool = new Pool(3);

        for (int i = 0; i < 5; i++) {
                new Thread(() ->{
                    try {
                        log.debug("获取连接");
                        DBConnection connection = pool.getConnection();

                        log.debug("业务处理中");
                        TimeUnit.SECONDS.sleep(1);

                        log.debug("释放连接");
                        pool.releaseConnection(connection);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                },"线程"+(i+1)).start();
        }

    }

}


@Slf4j
class Pool {

    private int poolSize;
    private DBConnection[] dbConnections;
    private AtomicInteger[] connectionStates ;
    private Semaphore semaphore;

    public Pool(int poolSize) {
        this.poolSize = poolSize;
        this.semaphore = new Semaphore(poolSize);
        this.dbConnections = new DBConnection[poolSize];
        for (int i = 0; i < poolSize; i++) {
            this.dbConnections[i] = new DBConnection();
        }
        this.connectionStates = new AtomicInteger[poolSize];
        for (int i = 0; i < poolSize; i++) {
            this.connectionStates[i] = new AtomicInteger(0);
        }
    }

    DBConnection getConnection() throws InterruptedException {

        while(true){
            semaphore.acquire();
            for (int i = 0; i < connectionStates.length; i++) {
                if(connectionStates[i].compareAndSet(0,1)){
                    return dbConnections[i];
                }
            }
        }

    }

    void releaseConnection(DBConnection dbConnection){
        for (int i = 0; i < dbConnections.length; i++) {
            if(dbConnection == dbConnections[i]){

                if(connectionStates[i].compareAndSet(1,0)){
                    semaphore.release();
                }
            }
        }
    }

}

class DBConnection{

}

11.4.3semaphore原理

 简单总结下semaphore原理:首先构造时 参数为3 那么 semaphore内 sync对象中的state就为3

然后线程访问时 如果同一时间超出这个数量,那么就去那个双向链表中park等着 等到前面有线程release后 state就减一 然后就unpark 唤醒 等待的线程

11.5 CountDownLatch

 简单来说类似于一个倒计时的工具 ,之前使用 join也可以实现,但是这个API更高级,使用场景举例:比如 五人跑步比赛,等五个人都准备好了 才能发令  比如游戏8个人玩,得至少6个人都加载游戏100%才能进

 示例代码

package com.robert.concurrent.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


@Slf4j(topic = "c.TestCountDown")
public class TestCountDown {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(() -> {
            try {
                log.debug("1运行中");
                TimeUnit.SECONDS.sleep(1);
                log.debug("1运行结束");
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                log.debug("2运行中");
                TimeUnit.SECONDS.sleep(1);
                log.debug("2运行结束");
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                log.debug("3运行中");
                TimeUnit.SECONDS.sleep(1);
                log.debug("3运行结束");
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        countDownLatch.await();
        log.debug("123 都运行结束 ");
    }


}

11.6 CyclicBarrier

与  CountDownLatch不同的是,这个东西能循环倒计时 从 3倒计时到0了 计数就重新变为3 所以不存在state 为 0 

并且它的构造方法中还可以传个ruannable任务,它会在 前面那些线程的代码执行完后 最后执行

可以用来统计啥的

package com.robert.concurrent.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;


@Slf4j(topic = "c.TestCountDown")
public class TestCyclicBarrier {

    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3,() -> {
            log.debug("都准备好,那么游戏开始 ");
        });

        for (int i = 1; i < 3; i++) {
            int teamNo = i;
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    log.debug(teamNo+"队 1号队员准备就绪");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();

            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    log.debug(teamNo+"队 2号队员准备就绪");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();

            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    log.debug(teamNo+"队 3号队员准备就绪");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }


}

11.7 ConcurrentHashMap

11.7.1 computeIfAbsent

HashMap是线程不安全的,比如如下代码 counter计数可能因为多线程并发而导致错误结果

 那么可以 使用ConcurrentHashMap 然后使用以下方法,跟redis的 setnx一样的作用,为什么不直接使用ConcurrentHashMap的get + set方法代替上面代码呢?因为它只能保证单个方法是原子性的

11.7.2 并发下扩容导致的死链引起OOM

正常情况下扩容是由下图一到下图二 这里只是简单说明,正常是元素个数达到阈值3/4size时才会扩容

 

死链就是头插法导致,  前一个线程 扩容然后rehash之后 数组1的位置 从  1->35->16->null 变为了 
35->1->null  16rehash后被放到另一个地方去了  第二个线程此时还在transfer方法再扩容 

因为是当节点不为null时一直遍历然后rehash  但是遍历到 35这个节点后下一个节点又是1 所以

1 ->35 ->1->35->1  就无限循环下去了   当然hashmap多线程情况下也本来就不安全

 

11.7.3 ConcurrentHashMap实现原理

cocurrenthashmap实现原理  

11.8LinkedBlockingQueue

数据结构挺简单如下

 

 put方法

 take方法

 

11.9ConcurrentLinkedQueue

11.10 CopyOnWriteArrayList

读写分离  弱一致性 适合读多写少的场景   


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我才是真的封不觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值