多线程理解part2

Synchronized

在这里插入图片描述
要保证在一个时刻,有且只有一个线程在操作共享数据,此时便引入了互斥锁
在这里插入图片描述
synchronized 便满足了以上特征
synchronized 锁的不是代码,锁的是对象,这个一定要记住,特别是在区别 访问被锁方法和被锁同步代码块时候
在这里插入图片描述
在这里插入图片描述
是同一个类,不管是不是同一个对象都会被锁住,顺序执行。
两种缩是没有关系的,互不干扰的。
在这里插入图片描述

底层实现原理在这里插入图片描述

hotspot 虚拟机
在这里插入图片描述
实例数据,对齐填充,需要自己看在这里插入图片描述
对象头 不存和类定义相关的数据,所以markword存储不固定的长度数据,存储对象运行时数据,会根据对象状态复用自身空间
在这里插入图片描述
在这里插入图片描述

重量级锁,指向的就是monitor对象的起始地址,每个对象都存在一个monitor与之关联,这两个对象的关联,存在多种实现方式,一起创建销毁or当前线程获取对象锁时候自动生成。
在HotSpot 里面是靠ObjectMonitor 实现的,在JVM虚拟机源码里面,通过C++实现的,ObjectMonitor.cpp里面
在这里插入图片描述
owner 里面存线程拥有者,count存储有几个线程持有,每个Java对象都拥有一个ObjectMonitor对象
在这里插入图片描述
javap看class里面的编译的字节码,标红的位置分别是同步代码块的开始位置和结束位置,可以重入(本身调用本身)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后对多一个exit ,为了异常处理,异常结束时候,为了释放,配对执行

方法同步是隐士的,如果抛出异常,并无法处理,异常并会被抛到同步方法外,自动释放资源
在这里插入图片描述
底层MutexLock 操作系统实现在这里插入图片描述
在这里插入图片描述

自旋锁与自适应自旋锁

在多CPU之后考虑
在这里插入图片描述
在这里插入图片描述
白白消耗CPU资源,所以要限制自旋次数,采用传统方式,挂起线程了

可以通过参数设置自旋次数,但这个次数交给人设置,多不好啊,所以就有了自适应自旋锁
在这里插入图片描述
之前得到过,并且正在运行中,JVM认为获取可能性很大,就会自动增加自旋时间 50次,相反会时间越来越少

锁消除

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

锁粗化

同步块尽可能小,另一个极端,就扩大
在这里插入图片描述
粗化到while之外

锁升级

升级过程 需要看
在这里插入图片描述
在这里插入图片描述

偏向锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解锁:
在这里插入图片描述

线程1当前拥有偏向锁对象,线程2是需要竞争到偏向锁。

线程2来竞争锁对象;
判断当前对象头是否是偏向锁;
判断拥有偏向锁的线程1是否还存在;
线程1不存在,直接设置偏向锁标识为0(线程1执行完毕后,不会主动去释放偏向锁);
使用cas替换偏向锁线程ID为线程2,锁不升级,仍为偏向锁;
线程1仍然存在,暂停线程1;
设置锁标志位为00(变为轻量级锁),偏向锁为0;
从线程1的空闲monitor record中读取一条,放至线程1的当前monitor record中;
更新mark word,将mark word指向线程1中monitor record的指针;
继续执行线程1的代码;
锁升级为轻量级锁;
线程2自旋来获取锁对象;
在这里插入图片描述
在这里插入图片描述

Synchronized 和ReentrantLock 区别

在这里插入图片描述
在这里插入图片描述
公平性会导致额外开销

在这里插入图片描述
在这里插入图片描述
Condition 将以上对象化

ArrayBlockingQueue 就是使用Condition

在这里插入图片描述

happens-before

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

volatile

在这里插入图片描述
在这里插入图片描述
不是线程安全的value++,所以必须sync修饰,

在这里插入图片描述
在这里插入图片描述
为何会立即可见在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
会拿到没有初始化的对象,就造成了线程安全问题

JVM并没有规定谁在前谁在后,那么就存在这种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了。

一定是volatile 的读写 happens-before 原则

在这里插入图片描述
使用volatile 避免步骤2和3的重排序
在这里插入图片描述

CAS

在这里插入图片描述
在这里插入图片描述
Unsafe 里面使用的CAS 是Native的
在这里插入图片描述
在这里插入图片描述

这就需要业务方面考虑是否要有ABA版本问题,会导致并发

Java 线程池

在这里插入图片描述
在这里插入图片描述
双端队列保存任务,工作线程从头,窃取线程从尾部

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
大部分业务使用给定的5类线程池,有些业务场景需要自己使用线程池

线程池的设计于实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
WorkerThead 被抽象成 Worker对象,继承自AQS

线程池处于shutdown状态,也需要策略去处理

在这里插入图片描述
在这里插入图片描述

如果阻塞队列满了,并且没有空闲线程,需要不同策略
thredFactory使用默认,会创建相同优先级的非守护线程

在这里插入图片描述
在这里插入图片描述
线程池记录线程状态优雅方式:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程池大小如何决定

在这里插入图片描述


版权声明:本文为u010122604原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。