基础概念(本章)
JUC同步工具(后续)
同步容器(后续)
线程池(后续)
高频面试和加分项(后续)
Disruptor mq框架,单机性能最高,效率最好(后续)
个人学习记录,不喜勿喷
目录
1,系统锁什么时候比自旋锁效率高?lock和synchroized 如何取舍
2,loadfence原语指令 ,storefence原语指令
day1
线程概念,常用方法,线程状态
一,synchronized基本概念
不能用String常量,Integer(值变了,就会变一个新对象) Long
"object"
String常量,所有用到都是同一个,假如都是一个类库的,不同的代码会是用的一把锁,死锁。
Object=null时能当锁么?编译不过,空指针异常
二,线程同步
-synchronized
.锁的是对象(类的前两位)不是代码
.thie Object.class
.锁定方法 非锁定方法,可以同时执行
.异常对同步块的影响
抛出异常会释放锁,会导致数据不一致,使用catch包裹可避免。
三,锁升级
synchroized的底层实现:
早期JDK: 重量级:OS找操作系统去申请锁;
改进1.5之后: 锁升级的概念-,搜索文字《我就是厕所所长》 马士兵
synchronized(object)
markword 对象头上前两位记录锁的类型,还记录这个线程ID (偏向锁)
如果有线程争用 : 升级为 自旋锁(占用CPU但不占用资源)
自循环while,10次后升级为重量级锁-OS
升级后,大多数情况下效率是很好的
只能升级,无法降级。两个线程都升级为OS之后,降到自旋锁效率会更高,但是降不下来了。
四,问题
1,系统锁什么时候比自旋锁效率高?lock和synchroized 如何取舍
执行时间长,线程数量少。反之自旋锁效率高
lock :CAS 一直自旋,会占用cpu
synchroized :升级到OS之后,会进入wait队列,不占用cpu时间
2,如何控制大对象
XX:PretenureSizeThreshold 默认值和作用
超过这个值对象直接在old区分配内存
默认值始0,意思是不管多大都先在eden中分配内存
day2
一,volatile的作用
1,保持线程的可见性
线程执行时,会产生副本,修改自身副本里的内容,读取中会被别人线程修改,导致值丧失一致性
加volatile之后,没次写都会被线程读到。
线程A对一个volatilevolatilevolatile变量的修改,对于其它线程来说是可见的(每次获取最新的)
2,MESI cpu缓存一致性协议
在早期的CPU中,是通过在总线加LOCK#锁的方式实现的,但是这种方式开销太大,所以Intel开发了缓存一致性协议,也就是MESI协议。
3,禁止指令重排序(cpu)
并发执行指令,编译器把指令重新排序。汇编语言的重排序
二,DCL单例
保证在JVM内存中,永远只有某一个类的一个实例。例如:权限
1.饿汉式
private static Object ob = new Object();
pulic static Object getOb(){
return this.ob ;
}
static保证只有一个实例,JVM做初始化。
不使用的时候会被实例化,要求使用的时候初始化
2.懒汉式
private static Object ob;
pulic static Object getOb(){
if(ob==null){
tob = new Object ();
}
return this.ob ;
}
多线程会出现问题,需要线程安全
3.懒汉式+方法锁
get的时候添加synchroized
private static Object ob;
pulic static Object synchroized getOb(){
if(ob==null){
tob = new Object ();
}
return this.ob ;
}
锁在方法上,方法实现如果太多代价太重了
4.懒汉式+代码块锁
在实例化的时候添加synchroized 块
private static Object ob;
pulic static Object getOb(){
if(ob==null){
synchroized(this){
tob = new Object();
}
}
return this.ob ;
}
仍有问题,多线程下为空时,会发生在同步块中同时new。
5.双重检查Double Check Lock
第一重判空的必要性:不申请锁,提高效率,多数情况下是不为空的。
private static Object ob;
pulic static Object getOb(){
if(ob==null){
synchroized(this){
if(ob==null){
tob = new Object();
}
}
}
return this.ob ;
}
很难复现,但依然会出现问题,问题出现在指令重排序上。ASM在三个指令码之间设置睡眠
6.双重检查+volatile
private static volatile Object ob;
pulic static Object getOb(){
if(ob==null){
synchroized(this){
if(ob==null){
tob = new Object();
}
}
}
return this.ob ;
}
三,创建对象JVm三个步骤
1,给这个对象申请内存 :a=0
2,给这个对象的成员变量初始化 :a=8
3,把这块内存的内容分配给这个对象 :栈内存指向a=8的地址
如果发生指令重排序,二三步颠倒了,半初始化的时候就赋值了,会发生还没有初始化的时候就把指向指定到a=0的地址,这个对象就不为空了,多线程读取的值是不同的。
相当于把半成品发布,虽然这个半成品会逐渐完善,但这个过程中充满了不确定。
帮助开阔思路,理解虚拟机。实际工作中直接用饿汉式,不会差那么一个对象的内存。
四,synchroized ,volatile 优缺点
synchroized 保证原子性,但是不能阻止重排序。
volatile 并不能保证多个线程共同修改变量时锁带来的不一致问题,都看到是同一个,但会同时做一样的事情,没有原子控制,多人做了重复的事情。(案例:多线程不用同步,操作一个volatile 修饰的变量。在操作方法上添加同步可以解决)
synchroized ZED优化
细化:业务逻辑中,sync加在具体 操作资源上,而不是整个方法;
粗化:业务逻辑中有多个块锁,整合在一起,减少系统征用。(查询数据库表时,每一行都加锁,不如加在表上)
锁定义时,如果发生改变,影响锁的使用
锁是锁定对象额头两位,锁本身发生变化时,会影响锁的使用。解决办法是添加final关键字
五,字节码
1,如何查看字节码视图?
打开idea 中的settings > plugins 搜索 jclasslib Bytecode Viewer 插件 安装重启生效
view - Show Bytecode With jclasslib -
public static void main(String argv[]){ T t = new T(); t.setA(8); }
0 new #2 <com/example/demo/com/T>
3 dup
4 invokespecial #3 <com/example/demo/com/T.<init>>
7 astore_1
8 aload_1
9 bipush 8
11 invokevirtual #4 <com/example/demo/com/T.setA>
14 return
Mgr06.java
2,loadfence原语指令 ,storefence原语指令
读写屏障
修饰引用类型时,一个线程内修改引用对象内的值,另一个线程无法获知,除非引用本身发生变化。
volatile int[] 当做锁,数组发生变化时,对锁的影响。
3,CAS(无锁(乐观锁)优化 自旋)
AtomXXX类
AtomicInteger
int类型的原子性操作,不用添加sync和volatile
.Compare And Set
比较和设置
.cas(V,Expected,NewValue)
当前值是我期望的值,操作该值。我设置的时候没人改变他的值,他是我期望的时候操作。
cas(V,期望值,更新值){
Expected = read v;
for(;;){
if V == Expected
V=NEW
else
不是期望的值,需要重新读取
}
}
OTHERWISE TRY AGAIN OR FAIL
CPU原语支持
cpu指令级别,不能被打断
4,ABA问题
先把1变成2,然后变成1。版本号
基础类型没问题,如果是引用就会有问题。object,对象的引用被改变,实际地址就改变了。(女朋友跟你复合,中间经历了别的女人,指针发生变化了?)
A 1.0
B 2.0
A 3.0
AtomicStampedReference
5,Unsafe (恶汉单例)
不能直接使用,只能在反射的时候使用(8,11可以使用)。
使用了弱指针的优势,垃圾回收的时候效率高。
操作内存,具备了C++的能力。
分配释放内存
allocateMemory freeMemory - > c malloc free - > c++ new delete
Unsafe = c c++的指针