阅读 113

Java开发常见面试题详解(并发,JVM)

Java开发常见面试题详解(并发,JVM)

预览

并发

问题 详解

请谈谈你对volatile的理解 link

CAS你知道吗? link

原子类Atomiclnteger的ABA问题谈谈?原子更新引用知道吗? link

我们知道ArrayList是线程不安全,请编码写一个不安全的案例并给出解决方案 link

公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁 link

CountDownLatch/CyclicBarrier/Semaphore使用过吗? link

阻塞队列知道吗? link

线程池用过吗?ThreadPoolExecutor谈谈你的理解? link

线程池用过吗?生产上你如何设置合理参数 link

死锁编码及定位分析 link

JVM

问题 详解

JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots link

你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值 link

你平时工作用过的JVM常用基本配置参数有哪些? link

强引用、软引用、弱引用、虚引用分别是什么?请谈谈你对OOM的认识 link

GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈怎么查看服务器默认的垃圾收集器是那个? link

生产上如何配置垃圾收集器的? link

谈谈你对垃圾收集器的理解?G1垃圾收集器 link

生产环境服务器变慢,诊断思路和性能评估谈谈? link

假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位 link

对于JDK自带的JVM监控和性能分析工具用过哪些?一般你是怎么用的? link

- - -

预览 01_本课程前提要求和说明 02_volatile是什么

03_JMM内存模型之可见性 04_可见性的代码验证说明 05_volatile不保证原子性

06_volatile不保证原子性理论解释 07_volatile不保证原子性问题解决 08_volatile指令重排案例1

09_volatile指令重排案例2 10_单例模式在多线程环境下可能存在安全问题 11_单例模式volatile分析

12_CAS是什么 13_CAS底层原理-上 14_CAS底层原理-下

15_CAS缺点 16_ABA问题 17_AtomicReference原子引用

18_AtomicStampedReference版本号原子引用 19_ABA问题的解决 20_集合类不安全之并发修改异常

21_集合类不安全之写时复制 22_集合类不安全之Set 23_集合类不安全之Map

24_TransferValue醒脑小练习 25_java锁之公平和非公平锁 26_java锁之可重入锁和递归锁理论知识

27_java锁之可重入锁和递归锁代码验证 28_java锁之自旋锁理论知识 29_java锁之自旋锁代码验证

30_java锁之读写锁理论知识 31_java锁之读写锁代码验证 32_CountDownLatch

33_CyclicBarrierDemo 34_SemaphoreDemo 35_阻塞队列理论

36_阻塞队列接口结构和实现类 37_阻塞队列api之抛出异常组 38_阻塞队列api之返回布尔值组

39_阻塞队列api之阻塞和超时控制 40_阻塞队列之同步SynchronousQueue队列 41_线程通信之生产者消费者传统版

42_Synchronized和Lock有什么区别 43_锁绑定多个条件Condition 44_线程通信之生产者消费者阻塞队列版

45_Callable接口 46_线程池使用及优势 47_线程池3个常用方式

48_线程池7大参数入门简介 49_线程池7大参数深入介绍 50_线程池底层工作原理

51_线程池的4种拒绝策略理论简介 52_线程池实际中使用哪一个 53_线程池的手写改造和拒绝策略

54_线程池配置合理线程数 55_死锁编码及定位分析 56_JVMGC下半场技术加强说明和前提知识要求

57_JVMGC快速回顾复习串讲 58_谈谈你对GCRoots的理解 59_JVM的标配参数和X参数

60_JVM的XX参数之布尔类型 61_JVM的XX参数之设值类型 62_JVM的XX参数之XmsXmx坑题

63_JVM盘点家底查看初始默认值 64_JVM盘点家底查看修改变更值 65_堆内存初始大小快速复习

66_常用基础参数栈内存Xss讲解 67_常用基础参数元空间MetaspaceSize讲解 68_常用基础参数PrintGCDetails回收前后对比讲解

69_常用基础参数SurvivorRatio讲解 70_常用基础参数NewRatio讲解 71_常用基础参数MaxTenuringThreshold讲解

72_强引用Reference 73_软引用SoftReference 74_弱引用WeakReference

75_软引用和弱引用的适用场景 76_WeakHashMap案例演示和解析 77_虚引用简介

78_ReferenceQueue引用队列介 79_虚引用PhantomReference 80_GCRoots和四大引用小总结

81_SOFE之StackOverflowError 82_OOM之Java heap space 83_OOM之GC overhead limit exceeded

84_OOM之Direct buffer memory 85_OOM之unable to create new native thread故障演示 86_OOM之unable to create new native thread上限调整

87_OOM之Metaspace 88_垃圾收集器回收种类 89_串行并行并发G1四大垃圾回收方式

90_如何查看默认的垃圾收集器 91_JVM默认的垃圾收集器有哪些 92_GC之7大垃圾收集器概述

93_GC之约定参数说明 94_GC之Serial收集器 95_GC之ParNew收集器

96_GC之Parallel收集器 97_GC之ParallelOld收集器 98_GC之CMS收集器

99_GC之SerialOld收集器 100_GC之如何选择垃圾收集器 101_GC之G1收集器

102_GC之G1底层原理 103_GC之G1参数配置及和CMS的比较 104_JVMGC结合SpringBoot微服务优化简介

105_Linux命令之top 106_Linux之cpu查看vmstat 107_Linux之cpu查看pidstat

108_Linux之内存查看free和pidstat 109_Linux之硬盘查看df 110_Linux之磁盘IO查看iostat和pidstat

111_Linux之网络IO查看ifstat 112_CPU占用过高的定位分析思路 113_GitHub骚操作之开启

114_GitHub骚操作之常用词 115_GitHub骚操作之in限制搜索 116_GitHub骚操作之star和fork范围搜索

117_GitHub骚操作之awesome搜索 118_GitHub骚操作之#L数字 119_GitHub骚操作之T搜索

120_GitHub骚操作之搜索区域活跃用户 - -

01_本课程前提要求和说明

教学视频


一些大厂的面试题


蚂蚁花呗一面:


Java容器有哪些?哪些是同步容器,哪些是并发容器?

ArrayList和LinkedList的插入和访问的时间复杂度?

java反射原理,注解原理?

新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?

HashMap在什么情况下会扩容,或者有哪些操作会导致扩容?

HashMap push方法的执行过程?

HashMap检测到hash冲突后,将元素插入在链表的末尾还是开头?

1.8还采用了红黑树,讲讲红黑树的特性,为什么人家一定要用红黑树而不是AVL、B树之类的?

https和http区别,有没有用过其他安全传输手段?

线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会怎么做,最后问阻塞队列的作用是什么?

linux怎么查看系统负载情况?

请详细描述springmvc处理请求全流程?spring 一个bean装配的过程?

讲一讲AtomicInteger,为什么要用CAS而不是synchronized?

美团一面:


最近做的比较熟悉的项目是哪个,画一下项目技术架构图。

JVM老年代和新生代的比例?

YGC和FGC发生的具体场景?

jstack,jmap,jutil分别的意义?如何线上排查JVM的相关问题?

线程池的构造类的方法的5个参数的具体意义?

单机上一个线程池正在处理服务如果忽然断电怎么办(正在处理和阻塞队列里的请求怎么处理)?

使用无界阻塞队列会出现什么问题?接口如何处理重复请求?

百度一面:


介绍一下集合框架?

hashmap hastable 底层实现什么区别?hashtable和concurrenthashtable呢?

hashmap和treemap什么区别?低层数据结构是什么?

线程池用过吗都有什么参数?底层如何实现的?

sychnized和Lock什么区别?sychnize 什么情况情况是对象锁?什么时候是全局锁为什么?

ThreadLocal 是什么底层如何实现?写一个例子呗?

volitile的工作原理?

cas知道吗如何实现的?

请用至少四种写法写一个单例模式?

请介绍一下JVM内存模型?用过什么垃圾回收器都说说呗线上发送频繁full gc如何处理?CPU使用率过高怎么办?如何定位问题?如何解决说一下解决思路和处理方法

知道字节码吗?字节码都有哪些?Integer x =5,int y =5,比较x =y 都经过哪些步骤?讲讲类加载机制呗都有哪些类加载器,这些类加载器都加载哪些文件?

手写一下类加载Demo

知道osgi吗?他是如何实现的?

请问你做过哪些JVM优化?使用什么方法达到什么效果?

classforName(“java.lang.String”)和String classgetClassLoader() LoadClass(“java.lang.String”)什么区别啊?

今日头条


HashMap如果一直put元素会怎么样? hashcode全都相同如何?

ApplicationContext的初始化过程?

GC 用什么收集器?收集的过程如何?哪些部分可以作为GC Root?

Volatile关键字,指令重排序有什么意义 ?synchronied,怎么用?

Redis数据结构有哪些?如何实现sorted set?

并发包里的原子类有哪些,怎么实现?

MvSql索引是什么数据结构? B tree有什么特点?优点是什么?

慢查询怎么优化?

项目: cache,各部分职责,有哪些优化点

京东金融面试


Dubbo超时重试;Dubbo超时时间设置

如何保障请求执行顺序

分布式事务与分布式锁(扣款不要出现负数)

分布式Session设置

执行某操作,前50次成功,第51次失败a全部回滚b前50次提交第51次抛异常,ab场景分别如何设计Spring (传播特性)

Zookeeper有却些作用

JVM内存模型

数据库垂直和水平拆分

MyBatis如何分页;如何设置缓存;MySQL分页

蚂蚁金服二面


自我介绍、工作经历、技术栈

项目中你学到了什么技术?(把三项目具体描述了很久)

微服务划分的粒度

微服务的高可用怎么保证的?

常用的负载均衡,该怎么用,你能说下吗?

网关能够为后端服务带来哪些好处?

Spring Bean的生命周期

HashSet是不是线程安全的?为什么不是线程安全的?

Java 中有哪些线程安全的Map?

Concurrenthashmap 是怎么做到线程安全的?

HashTable你了解过吗?

如何保证线程安全问题?

synchronized、lock

volatile 的原子性问题?为什么i++这种不支持原子性﹖从计算机原理的设计来讲下不能保证原子性的原因

happens before 原理

cas操作

lock和 synchronized 的区别?

公平锁和非公平锁

Java读写锁

读写锁设计主要解决什么问题?

02_volatile是什么

volatile是JVM提供的轻量级的同步机制


保证可见性

不保证原子性

禁止指令重排(保证有序性)

03_JMM内存模型之可见性

JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。


JMM关于同步的规定:


线程解锁前,必须把共享变量的值刷新回主内存

线程加锁前,必须读取主内存的最新值到自己的工作内存

加锁解锁是同一把锁

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:




可见性


通过前面对JMM的介绍,我们知道各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后再写回到主内存中的。


这就可能存在一个线程AAA修改了共享变量X的值但还未写回主内存时,另外一个线程BBB又对主内存中同一个共享变量X进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题


04_可见性的代码验证说明

import java.util.concurrent.TimeUnit;


/**

 * 假设是主物理内存

 */

class MyData {


    //volatile int number = 0;

    int number = 0;


    public void addTo60() {

        this.number = 60;

    }

}


/**

 * 验证volatile的可见性

 * 1. 假设int number = 0, number变量之前没有添加volatile关键字修饰

 */

public class VolatileDemo {


    public static void main(String args []) {


        // 资源类

        MyData myData = new MyData();


        // AAA线程 实现了Runnable接口的,lambda表达式

        new Thread(() -> {


            System.out.println(Thread.currentThread().getName() + "\t come in");


            // 线程睡眠3秒,假设在进行运算

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            // 修改number的值

            myData.addTo60();


            // 输出修改后的值

            System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number);


        }, "AAA").start();


        // main线程就一直在这里等待循环,直到number的值不等于零

        while(myData.number == 0) {}


        // 按道理这个值是不可能打印出来的,因为主线程运行的时候,number的值为0,所以一直在循环

        // 如果能输出这句话,说明AAA线程在睡眠3秒后,更新的number的值,重新写入到主内存,并被main线程感知到了

        System.out.println(Thread.currentThread().getName() + "\t mission is over");


    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

由于没有volatile修饰MyData类的成员变量number,main线程将会卡在while(myData.number == 0) {},不能正常结束。若想正确结束,用volatile修饰MyData类的成员变量number吧。


volatile类比


没有volatile修饰变量效果,相当于A同学拷贝了老师同一课件,A同学对课件进一步的总结归纳,形成自己的课件,这就与老师的课件不同了。


有volatile修饰变量效果,相当于A同学拷贝了老师同一课件,A同学对课件进一步的总结归纳,形成自己的课件,并且与老师分享,老师认可A同学修改后的课件,并用它来作下一届的课件。


05_volatile不保证原子性

原子性指的是什么意思?


不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整要么同时成功,要么同时失败。


volatile不保证原子性案例演示:


class MyData2 {

    /**

     * volatile 修饰的关键字,是为了增加 主线程和线程之间的可见性,只要有一个线程修改了内存中的值,其它线程也能马上感知

     */

    volatile int number = 0;



    public void addPlusPlus() {

        number ++;

    }

}


public class VolatileAtomicityDemo {


public static void main(String[] args) {

        MyData2 myData = new MyData2();


        // 创建10个线程,线程里面进行1000次循环

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

            new Thread(() -> {

                // 里面

                for (int j = 0; j < 1000; j++) {

                    myData.addPlusPlus();

                }

            }, String.valueOf(i)).start();

        }


        // 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值

        // 这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程

        while(Thread.activeCount() > 2) {

            // yield表示不执行

            Thread.yield();

        }


        // 查看最终的值

        // 假设volatile保证原子性,那么输出的值应该为:  20 * 1000 = 20000

        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);


}


}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

最后的结果总是小于20000。


06_volatile不保证原子性理论解释

number++在多线程下是非线程安全的。


我们可以将代码编译成字节码,可看出number++被编译成3条指令。




假设我们没有加 synchronized那么第一步就可能存在着,三个线程同时通过getfield命令,拿到主存中的 n值,然后三个线程,各自在自己的工作内存中进行加1操作,但他们并发进行 iadd 命令的时候,因为只能一个进行写,所以其它操作会被挂起,假设1线程,先进行了写操作,在写完后,volatile的可见性,应该需要告诉其它两个线程,主内存的值已经被修改了,但是因为太快了,其它两个线程,陆续执行 iadd命令,进行写入操作,这就造成了其他线程没有接受到主内存n的改变,从而覆盖了原来的值,出现写丢失,这样也就让最终的结果少于20000。


07_volatile不保证原子性问题解决

可加synchronized解决,但它是重量级同步机制,性能上有所顾虑。


如何不加synchronized解决number++在多线程下是非线程安全的问题?使用AtomicInteger。


import java.util.concurrent.atomic.AtomicInteger;


class MyData2 {

    /**

     * volatile 修饰的关键字,是为了增加 主线程和线程之间的可见性,只要有一个线程修改了内存中的值,其它线程也能马上感知

     */

volatile int number = 0;

AtomicInteger number2 = new AtomicInteger();


    public void addPlusPlus() {

        number ++;

    }

    

    public void addPlusPlus2() {

    number2.getAndIncrement();

    }

}


public class VolatileAtomicityDemo {


public static void main(String[] args) {

        MyData2 myData = new MyData2();


        // 创建10个线程,线程里面进行1000次循环

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

            new Thread(() -> {

                // 里面

                for (int j = 0; j < 1000; j++) {

                    myData.addPlusPlus();

                    myData.addPlusPlus2();

                }

            }, String.valueOf(i)).start();

        }


        // 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值

        // 这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程

        while(Thread.activeCount() > 2) {

            // yield表示不执行

            Thread.yield();

        }


        // 查看最终的值

        // 假设volatile保证原子性,那么输出的值应该为:  20 * 1000 = 20000

        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);

        System.out.println(Thread.currentThread().getName() + "\t finally number2 value: " + myData.number2);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

输出结果为:


main finally number value: 18766

main finally number2 value: 20000

1

2

08_volatile指令重排案例1

计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,一般分以下3种:




单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。


处理器在进行重排序时必须要考虑指令之间的数据依赖性


多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。


重排案例


public void mySort{

int x = 11;//语句1

    int y = 12;//语句2

    × = × + 5;//语句3

    y = x * x;//语句4

}

1

2

3

4

5

6

可重排序列:


1234

2134

1324

问题:请问语句4可以重排后变成第一个条吗?答:不能。


重排案例2


int a,b,x,y = 0


线程1 线程2

x = a; y = b;

b = 1; a = 2;

x = 0; y = 0

如果编译器对这段程序代码执行重排优化后,可能出现下列情况:


线程1 线程2

b = 1; a = 2;

x = a; y = b;

x = 2; y = 1

这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。


09_volatile指令重排案例2

观察以下程序:


public class ReSortSeqDemo{

int a = 0;

boolean flag = false;

    

public void method01(){

a = 1;//语句1

flag = true;//语句2

}

    

    public void method02(){

        if(flag){

            a = a + 5; //语句3

        }

        System.out.println("retValue: " + a);//可能是6或1或5或0

    }

    

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

多线程环境中线程交替执行method01()和method02(),由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。


禁止指令重排小总结


volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象


先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:


保证特定操作的执行顺序,

保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。


对volatile变量进行写操作时,会在写操作后加入一条store屏障指令,将工作内存中的共享变量值刷新回到主内存。




对Volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。




线性安全性获得保证


工作内存与主内存同步延迟现象导致的可见性问题 - 可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。


对于指令重排导致的可见性问题和有序性问题 - 可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。


10_单例模式在多线程环境下可能存在安全问题

懒汉单例模式


public class SingletonDemo {


    private static SingletonDemo instance = null;


    private SingletonDemo () {

        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo");

    }


    public static SingletonDemo getInstance() {

        if(instance == null) {

            instance = new SingletonDemo();

        }

        return instance;

    }


    public static void main(String[] args) {

        // 这里的 == 是比较内存地址

        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

输出结果:


main    我是构造方法singletonDemo

true

true

true

true

1

2

3

4

5

但是,在多线程环境运行上述代码,能保证单例吗?


public class SingletonDemo {


    private static SingletonDemo instance = null;


    private SingletonDemo () {

        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo");

    }


    public static SingletonDemo getInstance() {

        if(instance == null) {

            instance = new SingletonDemo();

        }

        return instance;

    }


    public static void main(String[] args) {

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

            new Thread(() -> {

                SingletonDemo.getInstance();

            }, String.valueOf(i)).start();

        }

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

输出结果:


4 我是构造方法SingletonDemo

2 我是构造方法SingletonDemo

5 我是构造方法SingletonDemo

6 我是构造方法SingletonDemo

0 我是构造方法SingletonDemo

3 我是构造方法SingletonDemo

1 我是构造方法SingletonDemo

1

2

3

4

5

6

7

显然不能保证单例。


解决方法之一:用synchronized修饰方法getInstance(),但它属重量级同步机制,使用时慎重。


public synchronized static SingletonDemo getInstance() {

    if(instance == null) {

        instance = new SingletonDemo();

    }

    return instance;

}

1

2

3

4

5

6

11_单例模式volatile分析

解决方法之二:DCL(Double Check Lock双端检锁机制)


public class SingletonDemo{

private SingletonDemo(){}

    

    private volatile static SingletonDemo instance = null;


    public static SingletonDemo getInstance() {

        if(instance == null) {

            synchronized(SingletonDemo.class){

                if(instance == null){

                    instance = new SingletonDemo();       

                }

            }

        }

        return instance;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

DCL中volatile解析


原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。instance = new SingletonDemo();可以分为以下3步完成(伪代码):


memory = allocate(); //1.分配对象内存空间

instance(memory); //2.初始化对象

instance = memory; //3.设置instance指向刚分配的内存地址,此时instance != null

1

2

3

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。


memory = allocate(); //1.分配对象内存空间

instance = memory;//3.设置instance指向刚分配的内存地址,此时instance! =null,但是对象还没有初始化完成!

instance(memory);//2.初始化对象

1

2

3

但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。


所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。


12_CAS是什么

Compare And Set


示例程序


public class CASDemo{

    public static void main(string[] args){

        AtomicInteger atomicInteger = new AtomicInteger(5);// mian do thing. . . . ..

        System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current data: "+atomicInteger.get());

        System.out.println(atomicInteger.compareAndset(5, 1024)+"\t current data: "+atomicInteger.get());

    }

}

1

2

3

4

5

6

7

输出结果为


true    2019

false   2019

1

2

13_CAS底层原理-上

Cas底层原理?如果知道,谈谈你对UnSafe的理解


atomiclnteger.getAndIncrement();源码


public class AtomicInteger extends Number implements java.io.Serializable {

    private static final long serialVersionUID = 6214790243416807050L;


    // setup to use Unsafe.compareAndSwapInt for updates

    private static final Unsafe unsafe = Unsafe.getUnsafe();

    private static final long valueOffset;


    static {

        try {

            valueOffset = unsafe.objectFieldOffset

                (AtomicInteger.class.getDeclaredField("value"));

        } catch (Exception ex) { throw new Error(ex); }

    }


    private volatile int value;

    

    /**

     * Creates a new AtomicInteger with the given initial value.

     *

     * @param initialValue the initial value

     */

    public AtomicInteger(int initialValue) {

        value = initialValue;

    }


    /**

     * Creates a new AtomicInteger with initial value {@code 0}.

     */

    public AtomicInteger() {

    }

    

    ...

            

    /**

     * Atomically increments by one the current value.

     *

     * @return the previous value

     */

    public final int getAndIncrement() {

        return unsafe.getAndAddInt(this, valueOffset, 1);

    }

    

    ...

}

    

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

UnSafe


1 Unsafe


是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。


注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。


2 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。


3 变量value用volatile修饰,保证了多线程之间的内存可见性。


CAS是什么


CAS的全称为Compare-And-Swap,它是一条CPU并发原语。


它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。


CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。(原子性)




14_CAS底层原理-下

继续上一节


UnSafe.getAndAddInt()源码解释:


var1 AtomicInteger对象本身。

var2 该对象值得引用地址。

var4 需要变动的数量。

var5是用过var1,var2找出的主内存中真实的值。

用该对象当前的值与var5比较:

如果相同,更新var5+var4并且返回true,

如果不同,继续取值然后再比较,直到更新完成。

假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上) :


Atomiclnteger里面的value原始值为3,即主内存中Atomiclnteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。

线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。

线程B也通过getintVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。

这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值己经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。

线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwaplnt进行比较替换,直到成功。

底层汇编


Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中。


UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)

UnsafeWrapper("Unsafe_CompareAndSwaplnt");

oop p = JNlHandles::resolve(obj);

jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);

return (jint)(Atomic::cmpxchg(x, addr, e))== e;

UNSAFE_END

//先想办法拿到变量value在内存中的地址。

//通过Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。

1

2

3

4

5

6

7

8

小结


CAS指令


CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。

当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。


15_CAS缺点

循环时间长开销很大


// ursafe.getAndAddInt

public final int getAndAddInt(Object var1, long var2, int var4){

int var5;

do {

var5 = this.getIntVolatile(var1, var2);

}while(!this.compareAndSwapInt(varl, var2, var5,var5 + var4));

    return var5;

}

1

2

3

4

5

6

7

8

我们可以看到getAndAddInt方法执行时,有个do while,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。


只能保证一个共享变量的原子操作


当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。


引出来ABA问题


16_ABA问题

ABA问题怎么产生的


CAS会导致“ABA问题”。


CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。


比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。


尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。


17_AtomicReference原子引用

import java.util.concurrent.atomic.AtomicReference;


class User{

String userName;

int age;

    public User(String userName, int age) {

this.userName = userName;

this.age = age;

}


@Override

public String toString() {

return String.format("User [userName=%s, age=%s]", userName, age);

}

    

}


public class AtomicReferenceDemo {

    public static void main(String[] args){

        User z3 = new User( "z3",22);

        User li4 = new User("li4" ,25);

AtomicReference<User> atomicReference = new AtomicReference<>();

        atomicReference.set(z3);

System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());

        System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());

    }

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

输出结果


true User [userName=li4, age=25]

false User [userName=li4, age=25]

1

2

18_AtomicStampedReference版本号原子引用

原子引用 + 新增一种机制,那就是修改版本号(类似时间戳),它用来解决ABA问题。


19_ABA问题的解决

ABA问题程序演示及解决方法演示:


import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;

import java.util.concurrent.atomic.AtomicStampedReference;


public class ABADemo {

/**

* 普通的原子引用包装类

*/

static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);


// 传递两个值,一个是初始值,一个是初始版本号

static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);


public static void main(String[] args) {


System.out.println("============以下是ABA问题的产生==========");


new Thread(() -> {

// 把100 改成 101 然后在改成100,也就是ABA

atomicReference.compareAndSet(100, 101);

atomicReference.compareAndSet(101, 100);

}, "t1").start();


new Thread(() -> {

try {

// 睡眠一秒,保证t1线程,完成了ABA操作

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

// 把100 改成 101 然后在改成100,也就是ABA

System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());


}, "t2").start();


/

try {

TimeUnit.SECONDS.sleep(2);

} catch (Exception e) {

e.printStackTrace();

}

/


System.out.println("============以下是ABA问题的解决==========");


new Thread(() -> {


// 获取版本号

int stamp = atomicStampedReference.getStamp();

System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);


// 暂停t3一秒钟

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}


// 传入4个值,期望值,更新值,期望版本号,更新版本号

atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),

atomicStampedReference.getStamp() + 1);


System.out.println(Thread.currentThread().getName() + "\t 第二次版本号" + atomicStampedReference.getStamp());


atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),

atomicStampedReference.getStamp() + 1);


System.out.println(Thread.currentThread().getName() + "\t 第三次版本号" + atomicStampedReference.getStamp());


}, "t3").start();


new Thread(() -> {


// 获取版本号

int stamp = atomicStampedReference.getStamp();

System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);


// 暂停t4 3秒钟,保证t3线程也进行一次ABA问题

try {

TimeUnit.SECONDS.sleep(3);

} catch (InterruptedException e) {

e.printStackTrace();

}


boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);


System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:"

+ atomicStampedReference.getStamp());


System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值" + atomicStampedReference.getReference());


}, "t4").start();


}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

输出结果


============以下是ABA问题的产生==========

true 2019

============以下是ABA问题的解决==========

t3 第一次版本号1

t4 第一次版本号1

t3 第二次版本号2

t3 第三次版本号3

t4 修改成功否:false 当前最新实际版本号:3

t4 当前实际最新值100

1

2

3

4

5

6

7

8

9

20_集合类不安全之并发修改异常

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.UUID;

import java.util.Vector;


public class ArrayListNotSafeDemo {

public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        //List<String> list = new Vector<>();

        //List<String> list = Collections.synchronizedList(new ArrayList<>());


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

            new Thread(() -> {

                list.add(UUID.randomUUID().toString().substring(0, 8));

                System.out.println(list);

            }, String.valueOf(i)).start();

        }

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

上述程序会抛java.util.ConcurrentModificationException


解决方法之一:Vector


解决方法之二:Collections.synchronizedList()


21_集合类不安全之写时复制

上一节程序导致抛java.util.ConcurrentModificationException的原因解析


先观察下抛错打印栈堆信息:


java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

at java.util.ArrayList$Itr.next(ArrayList.java:859)

at java.util.AbstractCollection.toString(AbstractCollection.java:461)

at java.lang.String.valueOf(String.java:2994)

at java.io.PrintStream.println(PrintStream.java:821)

at com.lun.collection.ArrayListNotSafeDemo.lambda$0(ArrayListNotSafeDemo.java:20)

at java.lang.Thread.run(Thread.java:748)

1

2

3

4

5

6

7

8

可看出toString(),Itr.next(),Itr.checkForComodification()后抛出异常,那么看看它们next(),checkForComodification()源码:


public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    

    ...

    

private class Itr implements Iterator<E> {

        int cursor;       // index of next element to return

        int lastRet = -1; // index of last element returned; -1 if no such

        int expectedModCount = modCount;//modCount在AbstractList类声明


        Itr() {}


        ...


        @SuppressWarnings("unchecked")

        public E next() {

            checkForComodification();

...

        }


        final void checkForComodification() {

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();//<---异常在此抛出

        }

    }

    

    

    public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e;

        return true;

    }

    

    private void ensureCapacityInternal(int minCapacity) {

        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

    }


    private void ensureExplicitCapacity(int minCapacity) {

        modCount++;//添加时,修改了modCount的值


        // overflow-conscious code

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

    

...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    ...


    protected transient int modCount = 0;

    

    ...

}

1

2

3

4

5

6

7

8

modCount具体详细说明如下:


The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.


This field is used by the iterator and list iterator implementation returned by the iterator and listIterator methods. If the value of this field changes unexpectedly, the iterator (or list iterator) will throw a ConcurrentModificationException in response to the next, remove, previous, set or add operations. This provides fail-fast behavior, rather than non-deterministic behavior in the face of concurrent modification during iteration.


link


综上所述,假设线程A将通过迭代器next()获取下一元素时,从而将其打印出来。但之前,其他某线程添加新元素至list,结构发生了改变,modCount自增。当线程A运行到checkForComodification(),expectedModCount是modCount之前自增的值,判定modCount != expectedModCount为真,继而抛出ConcurrentModificationException。


解决方法之三:CopyOnWriteArrayList(推荐)


public class CopyOnWriteArrayList<E>

    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {


    /** The array, accessed only via getArray/setArray. */

    private transient volatile Object[] array;

    

    final Object[] getArray() {

        return array;

    }


    final void setArray(Object[] a) {

        array = a;

    }

    

    ...

    

public boolean add(E e) {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            Object[] elements = getArray();

            int len = elements.length;

            Object[] newElements = Arrays.copyOf(elements, len + 1);

            newElements[len] = e;

            setArray(newElements);

            return true;

        } finally {

            lock.unlock();

        }

    }

    

    ...

    

    public String toString() {

        return Arrays.toString(getArray());

    }

    

    ...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

CopyOnWrite容器即写时复制的容器。待一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newELements,然后新的容器Object[ ] newELements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray (newELements)。


这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁(区别于Vector和Collections.synchronizedList()),因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。


22_集合类不安全之Set

HashSet也是非线性安全的。(HashSet内部是包装了一个HashMap的)


import java.util.Collections;

import java.util.HashSet;

import java.util.Set;

import java.util.UUID;

import java.util.concurrent.CopyOnWriteArraySet;


public class SetNotSafeDemo {

public static void main(String[] args) {

Set<String> set = new HashSet<>();

//Set<String> set = Collections.synchronizedSet(new HashSet<>());

//Set<String> set = new CopyOnWriteArraySet<String>();

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

            new Thread(() -> {

                set.add(UUID.randomUUID().toString().substring(0, 8));

                System.out.println(set);

            }, String.valueOf(i)).start();

        }

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

解决方法:


Collections.synchronizedSet(new HashSet<>())

CopyOnWriteArraySet<>()(推荐)

CopyOnWriteArraySet源码一览:


public class CopyOnWriteArraySet<E> extends AbstractSet<E>

        implements java.io.Serializable {

    private static final long serialVersionUID = 5457747651344034263L;


    private final CopyOnWriteArrayList<E> al;


    /**

     * Creates an empty set.

     */

    public CopyOnWriteArraySet() {

        al = new CopyOnWriteArrayList<E>();

    }


    public CopyOnWriteArraySet(Collection<? extends E> c) {

        if (c.getClass() == CopyOnWriteArraySet.class) {

            @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =

                (CopyOnWriteArraySet<E>)c;

            al = new CopyOnWriteArrayList<E>(cc.al);

        }

        else {

            al = new CopyOnWriteArrayList<E>();

            al.addAllAbsent(c);

        }

    }

 

    //可看出CopyOnWriteArraySet包装了一个CopyOnWriteArrayList

    

    ...

    

    public boolean add(E e) {

        return al.addIfAbsent(e);

    }

    

    public boolean addIfAbsent(E e) {

        Object[] snapshot = getArray();

        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :

            addIfAbsent(e, snapshot);

    }

    

    //暴力查找

    private static int indexOf(Object o, Object[] elements,

                               int index, int fence) {

        if (o == null) {

            for (int i = index; i < fence; i++)

                if (elements[i] == null)

                    return i;

        } else {

            for (int i = index; i < fence; i++)

                if (o.equals(elements[i]))

                    return i;

        }

        return -1;

    }


    private boolean addIfAbsent(E e, Object[] snapshot) {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            Object[] current = getArray();

            int len = current.length;

            if (snapshot != current) {//还要检查多一次元素存在性,生怕别的线程已经插入了

                // Optimize for lost race to another addXXX operation

                int common = Math.min(snapshot.length, len);

                for (int i = 0; i < common; i++)

                    if (current[i] != snapshot[i] && eq(e, current[i]))

                        return false;

                if (indexOf(e, current, common, len) >= 0)

                        return false;

            }

            Object[] newElements = Arrays.copyOf(current, len + 1);

            newElements[len] = e;

            setArray(newElements);

            return true;

        } finally {

            lock.unlock();

        }

    }

    

    ...

        

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

23_集合类不安全之Map

import java.util.Collections;

import java.util.HashMap;

import java.util.Hashtable;

import java.util.Map;

import java.util.UUID;

import java.util.concurrent.ConcurrentHashMap;


public class MapNotSafeDemo {


public static void main(String[] args) {

        Map<String, String> map = new HashMap<>();

//        Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

// Map<String, String> map = new ConcurrentHashMap<>();

// Map<String, String> map = new Hashtable<>();

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

            new Thread(() -> {

                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));

                System.out.println(map);

            }, String.valueOf(i)).start();

        }


}


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

解决方法:


HashTable

Collections.synchronizedMap(new HashMap<>())

ConcurrencyMap<>()(推荐)

24_TransferValue醒脑小练习

Java的参数传递是值传递,不是引用传递。


下面程序体验下上一句的含义:


class Person {

    private Integer id;

    private String personName;


    public Person(String personName) {

        this.personName = personName;

    }


public Integer getId() {

return id;

}


public void setId(Integer id) {

this.id = id;

}


public String getPersonName() {

return personName;

}


public void setPersonName(String personName) {

this.personName = personName;

}

}


public class TransferValueDemo {

    public void changeValue1(int age) {

        age = 30;

    }


    public void changeValue2(Person person) {

        person.setPersonName("XXXX");

    }

    public void changeValue3(String str) {

        str = "XXX";

    }


    public static void main(String[] args) {

        TransferValueDemo test = new TransferValueDemo();


        // 定义基本数据类型

        int age = 20;

        test.changeValue1(age);

        System.out.println("age ----" + age);


        // 实例化person类

        Person person = new Person("abc");

        test.changeValue2(person);

        System.out.println("personName-----" + person.getPersonName());


        // String

        String str = "abc";

        test.changeValue3(str);

        System.out.println("string-----" + str);


    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

输出结果:


age ----20

personName-----XXXX

string-----abc

1

2

3

25_java锁之公平和非公平锁

是什么


公平锁―是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。


非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁。在高并发的情况下,有可能会造成优先级反转或者饥饿现象


并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁。


The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation.


Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock. Also note that the untimed tryLock() method does not honor the fairness setting. It will succeed if the lock is available even if other threads are waiting.


此类的构造函数接受可选的公平性参数。当设置为true时,在争用下,锁有利于向等待时间最长的线程授予访问权限。否则,此锁不保证任何特定的访问顺序。与使用默认设置的程序相比,使用由许多线程访问的公平锁的程序可能显示出较低的总体吞吐量(即,较慢;通常要慢得多),但是在获得锁和保证没有饥饿的时间上差异较小。


但是请注意,锁的公平性并不能保证线程调度的公平性。因此,使用公平锁的多个线程中的一个线程可以连续多次获得公平锁,而其他活动线程则没有进行并且当前没有持有该锁。还要注意,不计时的 tryLock()方法不支持公平性设置。如果锁可用,即使其他线程正在等待,它也会成功。


link


reentrant

英 [riːˈɛntrənt] 美 [ˌriˈɛntrənt]

a. 可重入;可重入的;重入;可再入的;重进入


两者区别


关于两者区别:


公平锁

Threads acquire a fair lock in the order in which they requested it.

公平锁就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁

a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lockhappens to be available when it is requested.

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

题外话


Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。


非公平锁的优点在于吞吐量比公平锁大。


对于Synchronized而言,也是一种非公平锁


26_java锁之可重入锁和递归锁理论知识

可重入锁(也叫做递归锁)


指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。


也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。


ReentrantLock/synchronized就是一个典型的可重入锁。


可重入锁最大的作用是避免死锁。


27_java锁之可重入锁和递归锁代码验证

Synchronized可入锁演示程序


class Phone {


    public synchronized void sendSMS() throws Exception{

        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");


        // 在同步方法中,调用另外一个同步方法

        sendEmail();

    }



    public synchronized void sendEmail() throws Exception{

        System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()");

    }

}


public class SynchronizedReentrantLockDemo {


public static void main(String[] args) {

        Phone phone = new Phone();


        // 两个线程操作资源列

        new Thread(() -> {

            try {

                phone.sendSMS();

            } catch (Exception e) {

                e.printStackTrace();

            }

        }, "t1").start();


        new Thread(() -> {

            try {

                phone.sendSMS();

            } catch (Exception e) {

                e.printStackTrace();

            }

        }, "t2").start();

}


}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

输出结果:


t1 invoked sendSMS()

11 invoked sendEmail()

t2 invoked sendSMS()

12 invoked sendEmail()

1

2

3

4

ReentrantLock可重入锁演示程序


import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


class Phone2 implements Runnable{


    Lock lock = new ReentrantLock();


    /**

     * set进去的时候,就加锁,调用set方法的时候,能否访问另外一个加锁的set方法

     */

    public void getLock() {

        lock.lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t get Lock");

            setLock();

        } finally {

            lock.unlock();

        }

    }


    public void setLock() {

        lock.lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t set Lock");

        } finally {

            lock.unlock();

        }

    }


    @Override

    public void run() {

        getLock();

    }

}


public class ReentrantLockDemo {



    public static void main(String[] args) {

        Phone2 phone = new Phone2();


        /**

         * 因为Phone实现了Runnable接口

         */

        Thread t3 = new Thread(phone, "t3");

        Thread t4 = new Thread(phone, "t4");

        t3.start();

        t4.start();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

输出结果


t3 get Lock

t3 set Lock

t4 get Lock

t4 set Lock

1

2

3

4

28_java锁之自旋锁理论知识

自旋锁(Spin Lock)


是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU


提到了互斥同步对性能最大的影响阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态完成,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程 “稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。


《深入理解JVM.2nd》Page 398


29_java锁之自旋锁代码验证

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;


public class SpinLockDemo {

    // 现在的泛型装的是Thread,原子引用线程

    AtomicReference<Thread>  atomicReference = new AtomicReference<>();


    public void myLock() {

        // 获取当前进来的线程

        Thread thread = Thread.currentThread();

        System.out.println(Thread.currentThread().getName() + "\t come in ");


        // 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋

        while(!atomicReference.compareAndSet(null, thread)) {

//摸鱼

        }

    }


    public void myUnLock() {

        // 获取当前进来的线程

        Thread thread = Thread.currentThread();


        // 自己用完了后,把atomicReference变成null

        atomicReference.compareAndSet(thread, null);


        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");

    }

    

public static void main(String[] args) {

        SpinLockDemo spinLockDemo = new SpinLockDemo();


        // 启动t1线程,开始操作

        new Thread(() -> {


            // 开始占有锁

            spinLockDemo.myLock();


            try {

                TimeUnit.SECONDS.sleep(5);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            // 开始释放锁

            spinLockDemo.myUnLock();


        }, "t1").start();



        // 让main线程暂停1秒,使得t1线程,先执行

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }


        // 1秒后,启动t2线程,开始占用这个锁

        new Thread(() -> {


            // 开始占有锁

            spinLockDemo.myLock();

            // 开始释放锁

            spinLockDemo.myUnLock();


        }, "t2").start();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

输出结果


t1 come in 

t2 come in 

t1 invoked myUnlock()

t2 invoked myUnlock()

1

2

3

4

30_java锁之读写锁理论知识

独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁


共享锁:指该锁可被多个线程所持有。


多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写。


对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。


读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。


31_java锁之读写锁代码验证

实现一个读写缓存的操作,假设开始没有加锁的时候,会出现什么情况


import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.TimeUnit;


class MyCache {


    private volatile Map<String, Object> map = new HashMap<>();


    public void put(String key, Object value) {

        System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);

        try {

            // 模拟网络拥堵,延迟0.3秒

            TimeUnit.MILLISECONDS.sleep(300);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        map.put(key, value);

        System.out.println(Thread.currentThread().getName() + "\t 写入完成");

    }


    public void get(String key) {

        System.out.println(Thread.currentThread().getName() + "\t 正在读取:");

        try {

            // 模拟网络拥堵,延迟0.3秒

            TimeUnit.MILLISECONDS.sleep(300);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        Object value = map.get(key);

        System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);

    }

}


public class ReadWriteWithoutLockDemo {


public static void main(String[] args) {

        MyCache myCache = new MyCache();

        // 线程操作资源类,5个线程写

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

            final int tempInt = i;

            new Thread(() -> {

                myCache.put(tempInt + "", tempInt +  "");

            }, String.valueOf(i)).start();

        }

        

        // 线程操作资源类, 5个线程读

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

            final int tempInt = i;

            new Thread(() -> {

                myCache.get(tempInt + "");

            }, String.valueOf(i)).start();

        }


}


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

输出结果:


0 正在写入:0

1 正在写入:1

3 正在写入:3

2 正在写入:2

4 正在写入:4

0 正在读取:

1 正在读取:

2 正在读取:

4 正在读取:

3 正在读取:

1 写入完成

4 写入完成

0 写入完成

2 写入完成

3 写入完成

3 读取完成:3

0 读取完成:0

2 读取完成:2

1 读取完成:null

4 读取完成:null

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

看到有些线程读取到null,可用ReentrantReadWriteLock解决


package com.lun.concurrency;


import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantReadWriteLock;


class MyCache2 {


    private volatile Map<String, Object> map = new HashMap<>();


    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();


    public void put(String key, Object value) {


        // 创建一个写锁

        rwLock.writeLock().lock();


        try {


            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);


            try {

                // 模拟网络拥堵,延迟0.3秒

                TimeUnit.MILLISECONDS.sleep(300);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            map.put(key, value);


            System.out.println(Thread.currentThread().getName() + "\t 写入完成");


        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            // 写锁 释放

            rwLock.writeLock().unlock();

        }

    }


    public void get(String key) {


        // 读锁

        rwLock.readLock().lock();

        try {


            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");


            try {

                // 模拟网络拥堵,延迟0.3秒

                TimeUnit.MILLISECONDS.sleep(300);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            Object value = map.get(key);


            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);


        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            // 读锁释放

            rwLock.readLock().unlock();

        }

    }


    public void clean() {

        map.clear();

    }



}


public class ReadWriteWithLockDemo {

    public static void main(String[] args) {


        MyCache2 myCache = new MyCache2();


        // 线程操作资源类,5个线程写

        for (int i = 1; i <= 5; i++) {

            // lambda表达式内部必须是final

            final int tempInt = i;

            new Thread(() -> {

                myCache.put(tempInt + "", tempInt +  "");

            }, String.valueOf(i)).start();

        }


        // 线程操作资源类, 5个线程读

        for (int i = 1; i <= 5; i++) {

            // lambda表达式内部必须是final

            final int tempInt = i;

            new Thread(() -> {

                myCache.get(tempInt + "");

            }, String.valueOf(i)).start();

        }

    }

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

输出结果:


1 正在写入:1

1 写入完成

2 正在写入:2

2 写入完成

3 正在写入:3

3 写入完成

5 正在写入:5

5 写入完成

4 正在写入:4

4 写入完成

2 正在读取:

3 正在读取:

1 正在读取:

5 正在读取:

4 正在读取:

3 读取完成:3

2 读取完成:2

1 读取完成:1

5 读取完成:5

4 读取完成:4

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

32_CountDownLatch

让一线程阻塞直到另一些线程完成一系列操作才被唤醒。


CountDownLatch主要有两个方法(await(),countDown())。


当一个或多个线程调用await()时,调用线程会被阻塞。其它线程调用countDown()会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。


latch

英 [lætʃ] 美 [lætʃ]

n. 门闩;插销;碰锁;弹簧锁

v. 用插销插上;用碰锁锁上


假设一个自习室里有7个人,其中有一个是班长,班长的主要职责就是在其它6个同学走了后,关灯,锁教室门,然后走人,因此班长是需要最后一个走的,那么有什么方法能够控制班长这个线程是最后一个执行,而其它线程是随机执行的


import java.util.concurrent.CountDownLatch;


public class CountDownLatchDemo {

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


        // 计数器

        CountDownLatch countDownLatch = new CountDownLatch(6);


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

            new Thread(() -> {

                System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");

                countDownLatch.countDown();

            }, String.valueOf(i)).start();

        }


        countDownLatch.await();


        System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");

    }

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

输出结果:


0 上完自习,离开教室

6 上完自习,离开教室

4 上完自习,离开教室

5 上完自习,离开教室

3 上完自习,离开教室

1 上完自习,离开教室

2 上完自习,离开教室

main 班长最后关门

1

2

3

4

5

6

7

8

温习枚举


枚举 + CountDownLatch


程序演示秦国统一六国


import java.util.Objects;


public enum CountryEnum {

ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");


private Integer retcode;

private String retMessage;


CountryEnum(Integer retcode, String retMessage) {

this.retcode = retcode;

this.retMessage = retMessage;

}


public static CountryEnum forEach_countryEnum(int index) {

CountryEnum[] myArray = CountryEnum.values();

for(CountryEnum ce : myArray) {

if(Objects.equals(index, ce.getRetcode())) {

return ce;

}

}

return null;

}


public Integer getRetcode() {

return retcode;

}


public void setRetcode(Integer retcode) {

this.retcode = retcode;

}


public String getRetMessage() {

return retMessage;

}


public void setRetMessage(String retMessage) {

this.retMessage = retMessage;

}


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

import java.util.concurrent.CountDownLatch;


public class UnifySixCountriesDemo {


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

        // 计数器

        CountDownLatch countDownLatch = new CountDownLatch(6);


        for (int i = 1; i <= 6; i++) {

            new Thread(() -> {

                System.out.println(Thread.currentThread().getName() + "国被灭了!");

                countDownLatch.countDown();

            }, CountryEnum.forEach_countryEnum(i).getRetMessage()).start();

        }


        countDownLatch.await();


        System.out.println(Thread.currentThread().getName() + " 秦国统一中原。");

}

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

输出结果:


齐国被灭了!

燕国被灭了!

楚国被灭了!

魏国被灭了!

韩国被灭了!

赵国被灭了!

main 秦国统一中原。

1

2

3

4

5

6

7

33_CyclicBarrierDemo

CyclicBarrier的字面意思就是可循环(Cyclic)使用的屏障(Barrier)。它要求做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await方法。


CyclicBarrier与CountDownLatch的区别:CyclicBarrier可重复多次,而CountDownLatch只能是一次。


程序演示集齐7个龙珠,召唤神龙


import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CyclicBarrier;


public class SummonTheDragonDemo {

    public static void main(String[] args) {

        /**

         * 定义一个循环屏障,参数1:需要累加的值,参数2 需要执行的方法

         */

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {

            System.out.println("召唤神龙");

        });


        for (int i = 1; i <= 7; i++) {

            final Integer tempInt = i;

            new Thread(() -> {

                System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");


                try {

                    // 先到的被阻塞,等全部线程完成后,才能执行方法

                    cyclicBarrier.await();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                } catch (BrokenBarrierException e) {

                    e.printStackTrace();

                }

            }, String.valueOf(i)).start();

        }

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

输出结果:


2 收集到 第2颗龙珠

6 收集到 第6颗龙珠

1 收集到 第1颗龙珠

7 收集到 第7颗龙珠

5 收集到 第5颗龙珠

4 收集到 第4颗龙珠

3 收集到 第3颗龙珠

召唤神龙

1

2

3

4

5

6

7

8

来自《Java编程思想》的例子,展现CyclicBarrier的可循环性:


import java.util.concurrent.*;

import java.util.*;


class Horse implements Runnable {

private static int counter = 0;

private final int id = counter++;

private int strides = 0;

private static Random rand = new Random(47);

private static CyclicBarrier barrier;


public Horse(CyclicBarrier b) {

barrier = b;

}


public synchronized int getStrides() {

return strides;

}


public void run() {

try {

while (!Thread.interrupted()) {//没有中断,就不断循环

synchronized (this) {

                    //模拟马单位时间的移动距离

strides += rand.nextInt(3); // Produces 0, 1 or 2

}

barrier.await();//<---等待其他马到齐到循环屏障

}

} catch (InterruptedException e) {

// A legitimate way to exit

} catch (BrokenBarrierException e) {

// This one we want to know about

throw new RuntimeException(e);

}

}


public String toString() {

return "Horse " + id + " ";

}


public String tracks() {

StringBuilder s = new StringBuilder();

for (int i = 0; i < getStrides(); i++)

s.append("*");

s.append(id);

return s.toString();

}

}


public class HorseRace {

static final int FINISH_LINE = 75;

private List<Horse> horses = new ArrayList<Horse>();

private ExecutorService exec = Executors.newCachedThreadPool();

private CyclicBarrier barrier;


public HorseRace(int nHorses, final int pause) {

        //初始化循环屏障

barrier = new CyclicBarrier(nHorses, new Runnable() {

// 循环多次执行的任务

public void run() {

                

                // The fence on the racetrack

StringBuilder s = new StringBuilder();

for (int i = 0; i < FINISH_LINE; i++)

s.append("="); 

System.out.println(s);

                

                //打印马移动距离

for (Horse horse : horses)

System.out.println(horse.tracks());

                

                //判断有没有马到终点了

for (Horse horse : horses)

if (horse.getStrides() >= FINISH_LINE) {

System.out.println(horse + "won!");

exec.shutdownNow();// 有只马跑赢了,所有任务都结束了

return;

}

                try {

TimeUnit.MILLISECONDS.sleep(pause);

} catch (InterruptedException e) {

System.out.println("barrier-action sleep interrupted");

}

}

});

// 开跑!

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

Horse horse = new Horse(barrier);

horses.add(horse);

exec.execute(horse);

}

}


public static void main(String[] args) {

int nHorses = 7;

int pause = 200;

new HorseRace(nHorses, pause);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

输出结果:


...省略一些...

===========================================================================

**********************************************************0

************************************************************1

******************************************************2

***********************************************************************3

*************************************************************************4

*****************************************************************5

*****************************************************************6

===========================================================================

**********************************************************0

************************************************************1

*******************************************************2

***********************************************************************3

**************************************************************************4

*****************************************************************5

*******************************************************************6

===========================================================================

***********************************************************0

*************************************************************1

*******************************************************2

***********************************************************************3

****************************************************************************4

*******************************************************************5

********************************************************************6

Horse 4 won!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

34_SemaphoreDemo

信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。


semaphore

英 [ˈseməfɔː®] 美 [ˈseməfɔːr]

n. 信号标;旗语

v. 打旗语;(用其他类似的信号系统)发信号


正常的锁(concurrency.locks或synchronized锁)在任何时刻都只允许一个任务访问一项资源,而 Semaphore允许n个任务同时访问这个资源。


模拟一个抢车位的场景,假设一共有6个车,3个停车位


import java.util.concurrent.Semaphore;

import java.util.concurrent.TimeUnit;


public class SemaphoreDemo {

    public static void main(String[] args) {


        /**

         * 初始化一个信号量为3,默认是false 非公平锁, 模拟3个停车位

         */

        Semaphore semaphore = new Semaphore(3, false);


        // 模拟6部车

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

            new Thread(() -> {

                try {

                    // 代表一辆车,已经占用了该车位

                    semaphore.acquire(); // 抢占


                    System.out.println(Thread.currentThread().getName() + "\t 抢到车位");


                    // 每个车停3秒

                    try {

                        TimeUnit.SECONDS.sleep(3);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }


                    System.out.println(Thread.currentThread().getName() + "\t 离开车位");


                } catch (InterruptedException e) {

                    e.printStackTrace();

                } finally {

                    // 释放停车位

                    semaphore.release();

                }

            }, String.valueOf(i)).start();

        }

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

输出结果:


1 抢到车位

2 抢到车位

0 抢到车位

0 离开车位

2 离开车位

1 离开车位

5 抢到车位

4 抢到车位

3 抢到车位

5 离开车位

4 离开车位

3 离开车位

1

2

3

4

5

6

7

8

9

10

11

12

35_阻塞队列理论

阻塞队列有没有好的一面


不得不阻塞,你如何管理


36_阻塞队列接口结构和实现类

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如下图所示:




线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素。


当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。


当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。


试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。


同样试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程从列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增


为什么用?有什么好处?


在多线程领域:所谓阻塞,在某些情况下余挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒


为什么需要BlockingQueue

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了


在Concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。


架构介绍




种类分析:


ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。

PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

DelayQueue:使用优先级队列实现妁延迟无界阻塞队列。

SynchronousQueue:不存储元素的阻塞队列。

LinkedTransferQueue:由链表结构绒成的无界阻塞队列。

LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

BlockingQueue的核心方法


方法类型 抛出异常 特殊值 阻塞 超时

插入 add(e) offer(e) put(e) offer(e,time,unit)

移除 remove() poll() take() poll(time,unit)

检查 element() peek() 不可用 不可用

性质 说明

抛出异常 当阻塞队列满时:在往队列中add插入元素会抛出 IIIegalStateException:Queue full

当阻塞队列空时:再往队列中remove移除元素,会抛出NoSuchException

特殊性 插入方法,成功true,失败false 

移除方法:成功返回出队列元素,队列没有就返回空

一直阻塞 当阻塞队列满时,生产者继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出。

当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。

超时退出 当阻塞队列满时,队里会阻塞生产者线程一定时间,超过限时后生产者线程会退出

37_阻塞队列api之抛出异常组

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;


public class BlockingQueueExceptionDemo {


public static void main(String[] args) {

BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);


System.out.println(blockingQueue.add("a"));

System.out.println(blockingQueue.add("b"));

System.out.println(blockingQueue.add("c"));


try {

//抛出 java.lang.IllegalStateException: Queue full

System.out.println(blockingQueue.add("XXX"));

} catch (Exception e) {

System.err.println(e);

}

System.out.println(blockingQueue.element());

///

System.out.println(blockingQueue.remove());

System.out.println(blockingQueue.remove());

System.out.println(blockingQueue.remove());

try {

//抛出 java.util.NoSuchElementException

System.out.println(blockingQueue.remove());

} catch (Exception e) {

System.err.println(e);

}


try {

//element()相当于peek(),但element()会抛NoSuchElementException

System.out.println(blockingQueue.element());

} catch (Exception e) {

System.err.println(e);

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

输出结果:


true

true

true

a

java.lang.IllegalStateException: Queue full

a

b

c

java.util.NoSuchElementException

java.util.NoSuchElementException

1

2

3

4

5

6

7

8

9

10

38_阻塞队列api之返回布尔值组

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;


public class BlockingQueueBooleanDemo {


public static void main(String[] args) {

BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);


System.out.println(blockingQueue.offer("a"));

System.out.println(blockingQueue.offer("b"));

System.out.println(blockingQueue.offer("c"));

System.out.println(blockingQueue.offer("d"));


System.out.println(blockingQueue.poll());

System.out.println(blockingQueue.poll());

System.out.println(blockingQueue.poll());

System.out.println(blockingQueue.poll());

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

输出结果:


true

true

true

false

a

b

c

null

1

2

3

4

5

6

7

8

39_阻塞队列api之阻塞和超时控制

队列阻塞演示:


import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.TimeUnit;


public class BlockingQueueBlockedDemo {


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

BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

new Thread(()->{

try {

blockingQueue.put("a");

blockingQueue.put("b");

blockingQueue.put("c");

blockingQueue.put("c");//将会阻塞,直到主线程take()

System.out.println("it was blocked.");


} catch (InterruptedException e) {

e.printStackTrace();

}

}).start();

TimeUnit.SECONDS.sleep(2);

try {

blockingQueue.take();

blockingQueue.take();

blockingQueue.take();

blockingQueue.take();

System.out.println("Blocking...");

blockingQueue.take();//将会阻塞

} catch (InterruptedException e) {

e.printStackTrace();

}

}


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

阻塞超时放弃演示


import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.TimeUnit;


public class BlockingQueueTimeoutDemo {


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

BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

System.out.println("Offer.");

System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));

System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));

System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));

System.out.println(blockingQueue.offer("d", 2L, TimeUnit.SECONDS));

System.out.println("Poll.");

System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));

System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));

System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));

System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));

}


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

输出结果:


Offer.

true

true

true

false

Poll.

a

b

c

null

1

2

3

4

5

6

7

8

9

10

40_阻塞队列之同步SynchronousQueue队列

SynchronousQueue没有容量。


与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。


每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。


A blocking queue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa. A synchronous queue does not have any internal capacity, not even a capacity of one.


You cannot peek at a synchronous queue because an element is only present when you try to remove it;


You cannot insert an element (using any method) unless another thread is trying to remove it;


You cannot iterate as there is nothing to iterate.


The head of the queue is the element that the first queued inserting thread is trying to add to the queue; if there is no such queued thread then no element is available for removal and poll() will return null. For purposes of other Collection methods (for example contains), a SynchronousQueue acts as an empty collection. This queue does not permit null elements.


link


import java.util.concurrent.BlockingQueue;

import java.util.concurrent.SynchronousQueue;

import java.util.concurrent.TimeUnit;


public class SynchronousQueueDemo {

public static void main(String[] args) {

BlockingQueue<String> blockingQueue = new SynchronousQueue<>();


new Thread(() -> {

    try {       

        System.out.println(Thread.currentThread().getName() + "\t put A ");

        blockingQueue.put("A");

       

        System.out.println(Thread.currentThread().getName() + "\t put B ");

        blockingQueue.put("B");        

        

        System.out.println(Thread.currentThread().getName() + "\t put C ");

        blockingQueue.put("C");        

        

    } catch (InterruptedException e) {

        e.printStackTrace();

    }

}, "t1").start();

new Thread(() -> {

try {

try {

TimeUnit.SECONDS.sleep(5);

} catch (InterruptedException e) {

e.printStackTrace();

}

blockingQueue.take();

System.out.println(Thread.currentThread().getName() + "\t take A ");

try {

TimeUnit.SECONDS.sleep(5);

} catch (InterruptedException e) {

e.printStackTrace();

}

blockingQueue.take();

System.out.println(Thread.currentThread().getName() + "\t take B ");

try {

TimeUnit.SECONDS.sleep(5);

} catch (InterruptedException e) {

e.printStackTrace();

}

blockingQueue.take();

System.out.println(Thread.currentThread().getName() + "\t take C ");

} catch (InterruptedException e) {

e.printStackTrace();

}

}, "t2").start();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

41_线程通信之生产者消费者传统版

阻塞队列用在哪里?


生产者消费者模式

传统版(synchronized, wait, notify)

阻塞队列版(lock, await, signal)

线程池

消息中间件

实现一个简单的生产者消费者模式


import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


class ShareData {


    private int number = 0;


    private Lock lock = new ReentrantLock();


    private Condition condition = lock.newCondition();


    public void increment() throws Exception{

        // 同步代码块,加锁

        lock.lock();

        try {

            // 判断

            while(number != 0) {

                // 等待不能生产

                condition.await();

            }


            // 干活

            number++;


            System.out.println(Thread.currentThread().getName() + "\t " + number);


            // 通知 唤醒

            condition.signalAll();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }


    public void decrement() throws Exception{

        // 同步代码块,加锁

        lock.lock();

        try {

            // 判断

            while(number == 0) {

                // 等待不能消费

                condition.await();

            }


            // 干活

            number--;


            System.out.println(Thread.currentThread().getName() + "\t " + number);


            // 通知 唤醒

            condition.signalAll();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

}


public class TraditionalProducerConsumerDemo {

    

public static void main(String[] args) {


        ShareData shareData = new ShareData();


        // t1线程,生产

        new Thread(() -> {

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

                try {

                    shareData.increment();

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }, "t1").start();


        // t2线程,消费

        new Thread(() -> {

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

                try {

                    shareData.decrement();

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }, "t2").start();

        

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

输出结果:


t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

t1 1

t2 0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

注意,increment()和decrement()内的


// 判断

while(number != 0) {

    // 等待不能生产

    condition.await();

}

1

2

3

4

5

不能用


// 判断

if(number != 0) {

    // 等待不能生产

    condition.await();

}

1

2

3

4

5

否则会出现虚假唤醒,出现异常状况。


42_Synchronized和Lock有什么区别

synchronized属于JVM层面,属于java的关键字

monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象 只能在同步块或者方法中才能调用 wait/ notify等方法)

Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁

使用方法:

synchronized:不需要用户去手动释放锁,当synchronized代码执行后,系统会自动让线程释放对锁的占用。

ReentrantLock:则需要用户去手动释放锁,若没有主动释放锁,就有可能出现死锁的现象,需要lock() 和 unlock() 配置try catch语句来完成

等待是否中断

synchronized:不可中断,除非抛出异常或者正常运行完成。

ReentrantLock:可中断,可以设置超时方法

设置超时方法,trylock(long timeout, TimeUnit unit)

lockInterrupible() 放代码块中,调用interrupt() 方法可以中断

加锁是否公平

synchronized:非公平锁

ReentrantLock:默认非公平锁,构造函数可以传递boolean值,true为公平锁,false为非公平锁

锁绑定多个条件Condition

synchronized:没有,要么随机,要么全部唤醒

ReentrantLock:用来实现分组唤醒需要唤醒的线程,可以精确唤醒,而不是像synchronized那样,要么随机,要么全部唤醒

43_锁绑定多个条件Condition

实现场景


多线程之间按顺序调用,实现 A-> B -> C 三个线程启动,要求如下:

AA打印5次,BB打印10次,CC打印15次

紧接着

AA打印5次,BB打印10次,CC打印15次

来10轮


import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


class ShareResource {

    // A 1   B 2   c 3

    private int number = 1;

    // 创建一个重入锁

    private Lock lock = new ReentrantLock();


    // 这三个相当于备用钥匙

    private Condition condition1 = lock.newCondition();

    private Condition condition2 = lock.newCondition();

    private Condition condition3 = lock.newCondition();


    public void print5() {

        lock.lock();

        try {

            // 判断

            while(number != 1) {

                // 不等于1,需要等待

                condition1.await();

            }


            // 干活

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

                System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);

            }


            // 唤醒 (干完活后,需要通知B线程执行)

            number = 2;

            // 通知2号去干活了

            condition2.signal();


        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }


    public void print10() {

        lock.lock();

        try {

            // 判断

            while(number != 2) {

                // 不等于1,需要等待

                condition2.await();

            }


            // 干活

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

                System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);

            }


            // 唤醒 (干完活后,需要通知C线程执行)

            number = 3;

            // 通知2号去干活了

            condition3.signal();


        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }


    public void print15() {

        lock.lock();

        try {

            // 判断

            while(number != 3) {

                // 不等于1,需要等待

                condition3.await();

            }


            // 干活

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

                System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);

            }


            // 唤醒 (干完活后,需要通知C线程执行)

            number = 1;

            // 通知1号去干活了

            condition1.signal();


        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

}


public class SynchronizedAndReentrantLockDemo {

    public static void main(String[] args) {


        ShareResource shareResource = new ShareResource();

        int num = 10;


        new Thread(() -> {

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

                    shareResource.print5();

            }

        }, "A").start();


        new Thread(() -> {

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

                shareResource.print10();

            }

        }, "B").start();


        new Thread(() -> {

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

                shareResource.print15();

            }

        }, "C").start();

    }

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

输出结果:


...

A 1 0

A 1 1

A 1 2

A 1 3

A 1 4

B 2 0

B 2 1

B 2 2

B 2 3

B 2 4

B 2 5

B 2 6

B 2 7

B 2 8

B 2 9

C 3 0

C 3 1

C 3 2

C 3 3

C 3 4

C 3 5

C 3 6

C 3 7

C 3 8

C 3 9

C 3 10

C 3 11

C 3 12

C 3 13

C 3 14

A 1 0

A 1 1

A 1 2

A 1 3

A 1 4

B 2 0

B 2 1

B 2 2

B 2 3

B 2 4

B 2 5

B 2 6

B 2 7

B 2 8

B 2 9

C 3 0

C 3 1

C 3 2

C 3 3

C 3 4

C 3 5

C 3 6

C 3 7

C 3 8

C 3 9

C 3 10

C 3 11

C 3 12

C 3 13

C 3 14

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

44_线程通信之生产者消费者阻塞队列版

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicInteger;


class MyResource {

    // 默认开启,进行生产消费

    // 这里用到了volatile是为了保持数据的可见性,也就是当TLAG修改时,要马上通知其它线程进行修改

    private volatile boolean FLAG = true;


    // 使用原子包装类,而不用number++

    private AtomicInteger atomicInteger = new AtomicInteger();


    // 这里不能为了满足条件,而实例化一个具体的SynchronousBlockingQueue

    BlockingQueue<String> blockingQueue = null;


    // 而应该采用依赖注入里面的,构造注入方法传入

    public MyResource(BlockingQueue<String> blockingQueue) {

        this.blockingQueue = blockingQueue;

        // 查询出传入的class是什么

        System.out.println(blockingQueue.getClass().getName());

    }



    public void myProducer() throws Exception{

        String data = null;

        boolean retValue;

        // 多线程环境的判断,一定要使用while进行,防止出现虚假唤醒

        // 当FLAG为true的时候,开始生产

        while(FLAG) {

            data = atomicInteger.incrementAndGet() + "";


            // 2秒存入1个data

            retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);

            if(retValue) {

                System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data  + "成功" );

            } else {

                System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data  + "失败" );

            }


            try {

                TimeUnit.SECONDS.sleep(1);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }


        System.out.println(Thread.currentThread().getName() + "\t 停止生产,表示FLAG=false,生产介绍");

    }



    public void myConsumer() throws Exception{

        String retValue;

        // 多线程环境的判断,一定要使用while进行,防止出现虚假唤醒

        // 当FLAG为true的时候,开始生产

        while(FLAG) {

            // 2秒存入1个data

            retValue = blockingQueue.poll(2L, TimeUnit.SECONDS);

            if(retValue != null && retValue != "") {

                System.out.println(Thread.currentThread().getName() + "\t 消费队列:" + retValue  + "成功" );

            } else {

                FLAG = false;

                System.out.println(Thread.currentThread().getName() + "\t 消费失败,队列中已为空,退出" );


                // 退出消费队列

                return;

            }

        }

    }


    /**

     * 停止生产的判断

     */

    public void stop() {

        this.FLAG = false;

    }


}

public class ProducerConsumerWithBlockingQueueDemo {

    public static void main(String[] args) {

        // 传入具体的实现类, ArrayBlockingQueue

        MyResource myResource = new MyResource(new ArrayBlockingQueue<String>(10));


        new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动\n\n");


            try {

                myResource.myProducer();

                System.out.println("\n");


            } catch (Exception e) {

                e.printStackTrace();

            }

        }, "producer").start();



        new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");


            try {

                myResource.myConsumer();

            } catch (Exception e) {

                e.printStackTrace();

            }

        }, "consumer").start();


        // 5秒后,停止生产和消费

        try {

            TimeUnit.SECONDS.sleep(5);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }



        System.out.println("\n\n5秒中后,生产和消费线程停止,线程结束");

        myResource.stop();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

输出结果:


java.util.concurrent.ArrayBlockingQueue

producer 生产线程启动



consumer 消费线程启动

producer 插入队列:1成功

consumer 消费队列:1成功

producer 插入队列:2成功

consumer 消费队列:2成功

producer 插入队列:3成功

consumer 消费队列:3成功

producer 插入队列:4成功

consumer 消费队列:4成功

producer 插入队列:5成功

consumer 消费队列:5成功



5秒中后,生产和消费线程停止,线程结束

producer 停止生产,表示FLAG=false,生产介绍



consumer 消费失败,队列中已为空,退出

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

45_Callable接口

Callable接口,是一种让线程执行完成后,能够返回结果的。


import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

import java.util.concurrent.TimeUnit;


class MyThread implements Callable<Integer> {


    @Override

    public Integer call() throws Exception {

        System.out.println(Thread.currentThread().getName() + " come in Callable");

        TimeUnit.SECONDS.sleep(2);

        return 1024;

    }

}


public class CallableDemo {

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

FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());

new Thread(futureTask, "A").start();

new Thread(futureTask, "B").start();//多个线程执行 一个FutureTask的时候,只会计算一次

// 输出FutureTask的返回值

System.out.println("result FutureTask " + futureTask.get());

}

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

46_线程池使用及优势

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。


它的主要特点为:线程复用,控制最大并发数,管理线程。


优点:


降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。

提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

47_线程池3个常用方式

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。




了解


Executors.newScheduledThreadPool()

Executors.newWorkStealingPool(int) - Java8新增,使用目前机器上可用的处理器作为它的并行级别

重点


Executors.newSingleThreadExecutor()

public static ExecutorService newSingleThreadExecutor() {

    return new FinalizableDelegatedExecutorService

        (new ThreadPoolExecutor(1, 1,

                                0L, TimeUnit.MILLISECONDS,

                                new LinkedBlockingQueue<Runnable>()));

}

1

2

3

4

5

6

主要特点如下:


创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue。

Executors.newFixedThreadPool(int)

public static ExecutorService newFixedThreadPool(int nThreads) {

    return new ThreadPoolExecutor(nThreads, nThreads,

                                  0L, TimeUnit.MILLISECONDS,

                                  new LinkedBlockingQueue<Runnable>());

}

1

2

3

4

5

主要特点如下:


创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue。

Executors.newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {

    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                  60L, TimeUnit.SECONDS,

                                  new SynchronousQueue<Runnable>());

}

1

2

3

4

5

主要特点如下:


创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


public class ThreadPoolDemo {

    public static void main(String[] args) {


    // 一池5个处理线程(用池化技术,一定要记得关闭)

//    ExecutorService threadPool = Executors.newFixedThreadPool(5);


    // 创建一个只有一个线程的线程池

//    ExecutorService threadPool = Executors.newSingleThreadExecutor();


    // 创建一个拥有N个线程的线程池,根据调度创建合适的线程

    ExecutorService threadPool = Executors.newCachedThreadPool();


        // 模拟10个用户来办理业务,每个用户就是一个来自外部请求线程

        try {


            // 循环十次,模拟业务办理,让5个线程处理这10个请求

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

                final int tempInt = i;

                threadPool.execute(() -> {

                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");

                });

            }

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            threadPool.shutdown();

        }

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

输出结果:


pool-1-thread-1 给用户:0 办理业务

pool-1-thread-6 给用户:5 办理业务

pool-1-thread-5 给用户:4 办理业务

pool-1-thread-2 给用户:1 办理业务

pool-1-thread-4 给用户:3 办理业务

pool-1-thread-3 给用户:2 办理业务

pool-1-thread-10 给用户:9 办理业务

pool-1-thread-9 给用户:8 办理业务

pool-1-thread-8 给用户:7 办理业务

pool-1-thread-7 给用户:6 办理业务

1

2

3

4

5

6

7

8

9

10

48_线程池7大参数入门简介

public class ThreadPoolExecutor extends AbstractExecutorService {

    

    ...

    

public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              ThreadFactory threadFactory,

                              RejectedExecutionHandler handler) {

        if (corePoolSize < 0 ||

            maximumPoolSize <= 0 ||

            maximumPoolSize < corePoolSize ||

            keepAliveTime < 0)

            throw new IllegalArgumentException();

        if (workQueue == null || threadFactory == null || handler == null)

            throw new NullPointerException();

        this.acc = System.getSecurityManager() == null ?

                null :

                AccessController.getContext();

        this.corePoolSize = corePoolSize;

        this.maximumPoolSize = maximumPoolSize;

        this.workQueue = workQueue;

        this.keepAliveTime = unit.toNanos(keepAliveTime);

        this.threadFactory = threadFactory;

        this.handler = handler;

    }

    

    ...

    

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

49_线程池7大参数深入介绍

corePoolSize:线程池中的常驻核心线程数


在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程。

当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1


keepAliveTime:多余的空闲线程的存活时间。


当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止

unit:keepAliveTime的单位。


workQueue:任务队列,被提交但尚未被执行的任务。


threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。


handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数( maximumPoolSize)。


50_线程池底层工作原理





在创建了线程池后,等待提交过来的任务请求。


当调用execute()方法添加一个请求任务时,线程池会做如下判断:


如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;

如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

当一个线程完成任务时,它会从队列中取下一个任务来执行。


当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:


如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉,所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

51_线程池的4种拒绝策略理论简介

等待队列也已经排满了,再也塞不下新任务了同时,线程池中的max线程也达到了,无法继续为新任务服务。


这时候我们就需要拒绝策略机制合理的处理这个问题。


JDK拒绝策略:


AbortPolicy(默认):直接抛出 RejectedExecutionException异常阻止系统正常运知。

CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了RejectedExecutionHandler接口。


52_线程池实际中使用哪一个

(超级大坑警告)你在工作中单一的/固定数的/可变的三种创建线程池的方法,你用那个多?


答案是一个都不用,我们生产上只能使用自定义的


Executors 中JDK已经给你提供了,为什么不用?


3.【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。


说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。


4.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。


说明:Executors 返回的线程池对象的弊端如下:


1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。


2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。


阿里巴巴《Java 开发手册》


53_线程池的手写改造和拒绝策略

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.LinkedBlockingQueue;

import java.util.concurrent.RejectedExecutionHandler;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;


public class MyThreadPoolExecutorDemo {


public static void doSomething(ExecutorService executorService, int numOfRequest) {

    

        try {


            System.out.println(((ThreadPoolExecutor)executorService).getRejectedExecutionHandler().getClass() + ":");

            TimeUnit.SECONDS.sleep(1);


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

                final int tempInt = i;

                executorService.execute(() -> {

                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");

                });

            }

            

            TimeUnit.SECONDS.sleep(1);

            System.out.println("\n\n");

            

        } catch (Exception e) {

        System.err.println(e);

        } finally {

            executorService.shutdown();

        }

}

public static ExecutorService newMyThreadPoolExecutor(int corePoolSize,

           int maximumPoolSize, int blockingQueueSize, RejectedExecutionHandler handler){

return new ThreadPoolExecutor(

                corePoolSize,

                maximumPoolSize,

                1,//keepAliveTime

                TimeUnit.SECONDS,

                new LinkedBlockingQueue<>(blockingQueueSize),

                Executors.defaultThreadFactory(),

                handler);

}

public static void main(String[] args) {

doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.AbortPolicy()), 10);

doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.CallerRunsPolicy()), 20);

doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardOldestPolicy()), 10);

doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardPolicy()), 10);

}


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

输出结果:


class java.util.concurrent.ThreadPoolExecutor$AbortPolicy:

pool-1-thread-1 给用户:0 办理业务

pool-1-thread-3 给用户:5 办理业务java.util.concurrent.RejectedExecutionException: Task com.lun.concurrency.MyThreadPoolExecutorDemo$$Lambda$1/303563356@eed1f14 rejected from java.util.concurrent.ThreadPoolExecutor@7229724f[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8]


pool-1-thread-2 给用户:1 办理业务

pool-1-thread-5 给用户:7 办理业务

pool-1-thread-3 给用户:3 办理业务

pool-1-thread-4 给用户:6 办理业务

pool-1-thread-1 给用户:2 办理业务

pool-1-thread-2 给用户:4 办理业务

class java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy:

pool-2-thread-1 给用户:0 办理业务

pool-2-thread-2 给用户:1 办理业务

pool-2-thread-1 给用户:2 办理业务

pool-2-thread-3 给用户:5 办理业务

pool-2-thread-3 给用户:7 办理业务

pool-2-thread-3 给用户:9 办理业务

pool-2-thread-4 给用户:6 办理业务

pool-2-thread-2 给用户:3 办理业务

pool-2-thread-5 给用户:8 办理业务

main 给用户:10 办理业务

pool-2-thread-1 给用户:4 办理业务

pool-2-thread-3 给用户:11 办理业务

pool-2-thread-4 给用户:13 办理业务

main 给用户:14 办理业务

pool-2-thread-1 给用户:12 办理业务

pool-2-thread-5 给用户:15 办理业务

pool-2-thread-2 给用户:17 办理业务

main 给用户:18 办理业务

pool-2-thread-3 给用户:16 办理业务

pool-2-thread-4 给用户:19 办理业务




class java.util.concurrent.ThreadPoolExecutor$DiscardOldestPolicy:

pool-3-thread-1 给用户:0 办理业务

pool-3-thread-2 给用户:1 办理业务

pool-3-thread-1 给用户:2 办理业务

pool-3-thread-2 给用户:3 办理业务

pool-3-thread-3 给用户:5 办理业务

pool-3-thread-5 给用户:8 办理业务

pool-3-thread-2 给用户:7 办理业务

pool-3-thread-4 给用户:6 办理业务

pool-3-thread-1 给用户:4 办理业务

pool-3-thread-3 给用户:9 办理业务




class java.util.concurrent.ThreadPoolExecutor$DiscardPolicy:

pool-4-thread-1 给用户:0 办理业务

pool-4-thread-2 给用户:1 办理业务

pool-4-thread-1 给用户:2 办理业务

pool-4-thread-2 给用户:3 办理业务

pool-4-thread-3 给用户:5 办理业务

pool-4-thread-3 给用户:9 办理业务

pool-4-thread-1 给用户:4 办理业务

pool-4-thread-5 给用户:8 办理业务

pool-4-thread-4 给用户:6 办理业务

pool-4-thread-2 给用户:7 办理业务





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

54_线程池配置合理线程数

合理配置线程池你是如何考虑的?


CPU密集型


CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。


CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),

而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。


CPU密集型任务配置尽可能少的线程数量:


一般公式:(CPU核数+1)个线程的线程池


lO密集型


由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数 * 2。


IO密集型,即该任务需要大量的IO,即大量的阻塞。


在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。


所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。


IO密集型时,大部分线程都阻塞,故需要多配置线程数:


参考公式:CPU核数/ (1-阻塞系数)


阻塞系数在0.8~0.9之间


比如8核CPU:8/(1-0.9)=80个线程数


55_死锁编码及定位分析

是什么


死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够碍到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。




产生死锁主要原因


系统资源不足

进程运行推进的顺序不合适

资源分配不当

发生死锁的四个条件:


互斥条件,线程使用的资源至少有一个不能共享的。

至少有一个线程必须持有一个资源且正在等待获取一个当前被别的线程持有的资源。

资源不能被抢占。

循环等待。

如何解决死锁问题


破坏发生死锁的四个条件其中之一即可。


产生死锁的代码(根据发生死锁的四个条件):


package com.lun.concurrency;


import java.util.concurrent.TimeUnit;


class MyTask implements Runnable{


private Object resourceA, resourceB;

public MyTask(Object resourceA, Object resourceB) {

this.resourceA = resourceA;

this.resourceB = resourceB;

}


@Override

public void run() {

synchronized (resourceA) {

System.out.println(String.format("%s 自己持有%s,尝试持有%s",// 

Thread.currentThread().getName(), resourceA, resourceB));

try {

TimeUnit.SECONDS.sleep(2);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (resourceB) {

System.out.println(String.format("%s 同时持有%s,%s",// 

Thread.currentThread().getName(), resourceA, resourceB));

}

}

}

}


public class DeadLockDemo {

public static void main(String[] args) {

Object resourceA = new Object();

Object resourceB = new Object();

new Thread(new MyTask(resourceA, resourceB),"Thread A").start();

new Thread(new MyTask(resourceB, resourceA),"Thread B").start();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

输出结果:


Thread A 自己持有java.lang.Object@59d8d77,尝试持有java.lang.Object@7a15e6e6

Thread B 自己持有java.lang.Object@7a15e6e6,尝试持有java.lang.Object@59d8d77

1

2

程序卡死,未出现同时持有的字样。


查看是否死锁工具:


jps命令定位进程号


jstack找到死锁查看


C:\Users\abc>jps -l

11968 com.lun.concurrency.DeadLockDemo

6100 jdk.jcmd/sun.tools.jps.Jps

6204 Eclipse


C:\Users\abc>jstack 11968

2021-03-09 02:42:46

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):


"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00000000004de800 nid=0x2524 waiting on condition [0

x0000000000000000]

   java.lang.Thread.State: RUNNABLE


"Thread B" #12 prio=5 os_prio=0 tid=0x000000001e0a5800 nid=0x6bc waiting for monitor entry [0x

000000001efae000]

   java.lang.Thread.State: BLOCKED (on object monitor)

        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)

        - waiting to lock <0x000000076b431d80> (a java.lang.Object)

        - locked <0x000000076b431d90> (a java.lang.Object)

        at java.lang.Thread.run(Thread.java:748)


"Thread A" #11 prio=5 os_prio=0 tid=0x000000001e0a4800 nid=0x650 waiting for monitor entry [0x

000000001eeae000]

   java.lang.Thread.State: BLOCKED (on object monitor)

        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)

        - waiting to lock <0x000000076b431d90> (a java.lang.Object)

        - locked <0x000000076b431d80> (a java.lang.Object)

        at java.lang.Thread.run(Thread.java:748)


"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001e034000 nid=0x2fb8 runnable [0x000

0000000000000]

   java.lang.Thread.State: RUNNABLE


"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000000001dffa000 nid=0x26e8 waiting on c

ondition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE


"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001dff6000 nid=0x484 waiting on co

ndition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE


"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001dfe0800 nid=0x35c8 waiting on c

ondition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE


"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001dfde800 nid=0x3b7c waiting on c

ondition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE


"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001dfdd000 nid=0x3834 waiting on cond

ition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE


"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001dfdb000 nid=0x214 runnable [0x00

00000000000000]

   java.lang.Thread.State: RUNNABLE


"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001df70800 nid=0x2650 in Object.wait() [0x0

00000001e54f000]

   java.lang.Thread.State: WAITING (on object monitor)

        at java.lang.Object.wait(Native Method)

        - waiting on <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)

        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)

        - locked <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)

        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)

        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)


"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001c17d000 nid=0x1680 in Object.wa

it() [0x000000001e44f000]

   java.lang.Thread.State: WAITING (on object monitor)

        at java.lang.Object.wait(Native Method)

        - waiting on <0x000000076b386c00> (a java.lang.ref.Reference$Lock)

        at java.lang.Object.wait(Object.java:502)

        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)

        - locked <0x000000076b386c00> (a java.lang.ref.Reference$Lock)

        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)


"VM Thread" os_prio=2 tid=0x000000001c178000 nid=0x3958 runnable


"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002667800 nid=0xd3c runnable


"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002669000 nid=0x297c runnable


"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000266a800 nid=0x2fd0 runnable


"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000266c000 nid=0x1c90 runnable


"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000266f800 nid=0x3614 runnable


"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002670800 nid=0x298c runnable


"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002674000 nid=0x2b40 runnable


"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002675000 nid=0x25f4 runnable


"VM Periodic Task Thread" os_prio=2 tid=0x000000001e097000 nid=0xd54 waiting on condition


JNI global references: 5



Found one Java-level deadlock:

=============================

"Thread B":

  waiting to lock monitor 0x000000001e105dc8 (object 0x000000076b431d80, a java.lang.Object),

  which is held by "Thread A"

"Thread A":

  waiting to lock monitor 0x000000001c181828 (object 0x000000076b431d90, a java.lang.Object),

  which is held by "Thread B"


Java stack information for the threads listed above:

===================================================

"Thread B":

        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)

        - waiting to lock <0x000000076b431d80> (a java.lang.Object)

        - locked <0x000000076b431d90> (a java.lang.Object)

        at java.lang.Thread.run(Thread.java:748)

"Thread A":

        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)

        - waiting to lock <0x000000076b431d90> (a java.lang.Object)

        - locked <0x000000076b431d80> (a java.lang.Object)

        at java.lang.Thread.run(Thread.java:748)


Found 1 deadlock.



C:\Users\abc>


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

56_JVMGC下半场技术加强说明和前提知识要求


57_JVMGC快速回顾复习串讲

JVM内存结构


JVM体系概述




Java8以后的JVM








常见的垃圾回收算法


1.引用计数




2.复制


Java堆从GC的角度还可以细分为: 新生代(Eden 区、From Survivor 区和To Survivor 区)和老年代。




MinorGC的过程(复制->清空->互换):

a. Eden、SurvivorFrom复制到SurvivorTo,年龄+1

首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1。


b. 清空eden-SurvivorErom

然后,清空Eden和Survivor From中的对象,也即复制之后有交换,谁空谁是To。


c. Survivor To和 Survivor From互换

最后,Survivor To和Survivor From互换,原SurvivorTo成为下一次GC时的Survivor From区。部分对象会在From和To区域中复制来复制去,如此交换15次(由ⅣM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。


标记清除


算法分成标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象。

————————————————

版权声明:本文为CSDN博主「巨輪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/u011863024/article/details/114684428


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐