基础概念(本章)
JUC同步工具(本章)
同步容器(后续)
线程池(后续)
高频面试和加分项(后续)
Disruptor mq框架,单机性能最高,效率最好(后续)
个人学习记录,不喜勿喷
day1
线程概念,常用方法,线程状态
synchronized(Object)不能用String常量,Integer(值变了,就会变一个新对象) Long
"object"
String常量,所有用到都是同一个,假如都是一个类库的,不同的代码会是用的一把锁,死锁。
Object=null时能当锁么?编译不过,空指针异常
线程同步 -synchronized.锁的是对象(类的前两位)不是代码
.thie Object.class
.锁定方法 非锁定方法,可以同时执行
.异常对同步块的影响
抛出异常会释放锁,会导致数据不一致,使用catch包裹可避免。
.锁升级synchroized的底层实现:
早期JDK: 重量级:OS找操作系统去申请锁;
改进1.5之后: 锁升级的概念---我就是厕所所长
sync(object)
markword 对象头上前两位记录锁的类型,还记录这个线程ID (偏向锁)
如果有线程争用 : 升级为 自旋锁(占用CPU但不占用资源)
自循环while,10次后升级为重量级锁-OS
升级后,大多数情况下效率是很好的
只能升级,无法降级。两个线程都升级为OS之后,降到自旋锁效率会更高,但是降不下来了。
系统锁什么时候比自旋锁效率高?
执行时间长,线程数量少。反之自旋锁效率高
lock :CAS 一直自旋,会占用cpu
synchroized :升级到OS之后,会进入wait队列,不占用cpu时间
day2 控制大对象XX:PretenureSizeThreshold 默认值和作用
超过这个值对象直接在old区分配内存
默认值始0,意思是不管多大都先在eden中分配内存
volatile .保持线程的可见性线程执行时,会产生副本,修改自身副本里的内容,读取中会被别人线程修改,导致值丧失一致性
加volatile之后,没次写都会被线程读到
- MESI - cpu缓存一致性协议 .禁止指令重排序(cpu)并发执行指令,编译器把指令重新排序。汇编语言的重排序
-DCL单例保证在JVM内存中,永远只有某一个类的一个实例。例如:权限
.饿汉式private static Object ob = new Object();
pulic static Object getOb(){
return this.ob ;
}
static保证只有一个实例,JVM做初始化。
不使用的时候会被实例化,要求使用的时候初始化
.懒汉式private static Object ob;
pulic static Object getOb(){
if(ob==null){
tob = new Object ();
}
return this.ob ;
}
多线程会出现问题,需要线程安全
.懒汉式+方法锁get的时候添加synchroized
private static Object ob;
pulic static Object synchroized getOb(){
if(ob==null){
tob = new Object ();
}
return this.ob ;
}
锁在方法上,方法实现如果太多代价太重了
.懒汉式+代码块锁在实例化的时候添加synchroized 块
private static Object ob;
pulic static Object getOb(){
if(ob==null){
synchroized(this){
tob = new Object();
}
}
return this.ob ;
}
仍有问题,多线程下为空时,会发生在同步块中同时new。
.双重检查第一重判空的必要性:不申请锁,提高效率,多数情况下是不为空的。
private static Object ob;
pulic static Object getOb(){
if(ob==null){
synchroized(this){
if(ob==null){
tob = new Object();
}
}
}
return this.ob ;
}
很难复现,但依然会出现问题,问题出现在指令重排序上。ASM在三个指令码之间设置睡眠
-Double Check Lock .双重检查+volatileprivate 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 并不能保证多个线程共同修改变量时锁带来的不一致问题,都看到是同一个,但会同时做一样的事情,没有原子控制,多人做了重复的事情。(案例:多线程不用同步,操作一个volatile 修饰的变量。在操作方法上添加同步可以解决)
synchroized ZED优化细化:业务逻辑中,sync加在具体 操作资源上,而不是整个方法;
粗化:业务逻辑中有多个块锁,整合在一起,减少系统征用。(查询数据库表时,每一行都加锁,不如加在表上)
锁定义时,如果发生改变,影响锁的使用
锁是锁定对象额头两位,锁本身发生变化时,会影响锁的使用。解决办法是添加final关键字
volatile int[] 当做锁,数组发生变化时,对锁的影响
-Mgr06.java
查看字节码视图
打开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
3 dup
4 invokespecial #3
7 astore_1
8 aload_1
9 bipush 8
11 invokevirtual #4
14 return
读写屏障
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指令级别,不能被打断
ABA问题先把1变成2,然后变成1。版本号
基础类型没问题,如果是引用就会有问题。object,对象的引用被改变,实际地址就改变了。(女朋友跟你复合,中间经历了别的女人,指针发生变化了?)
A 1.0
B 2.0
A 3.0
AtomicStampedReference
Unsafe (恶汉单例)不能直接使用,只能在反射的时候使用(8,11可以使用)。
使用了弱指针的优势,垃圾回收的时候效率高。
操作内存,具备了C++的能力。
分配释放内存
allocateMemory freeMemory - > c malloc free - > c++ new delete
Unsafe = c c++的指针
day3 JUC同步工具 LongAdder > Atomic > sync 高并发下效率高Atomic > sync
CAS(无锁(乐观锁)优化 自旋)
线程多,操作时间短的情况下,自旋锁大于系统锁的性能
LongAdder > Atomic
CAS(无锁(乐观锁)优化 自旋)的基础上
内部分段:例如1000个线程,分成4个250个线程在一起工作,然后把这个4个一加。
线程数特别多,有明显优势。
LOCK -可重入锁可重入锁:锁一次后,可以对同样的锁再锁一次 。线程1获得锁,当线程1执行完任务再次进入的时候,在锁的头上记录有线程的标识号,对比后发现一致,继续执行,不需要额外申请。
子类实现调用父类。
sync m1 {}
sync m2 {}
new Thread(rl:m1).startt();
new Thread(rl:m2).startt();
//这时1先拿到锁,2永远最后执行
//但是如果在1里面调用2,两个方法都可以执行。
sync 方法 == sync(this) 是同一把锁
.reentrantLock使用reentrantLock 重写例子,与sync 不同的是,需要在finally里解锁
.tryLock 尝试申请时间在指定之间内,尝试获取锁
.lockInterruptibly 可以对interrput()方法做出响应sync
一旦wait之后,必须notify才能醒来。
lock
可以被打断的加锁,使用interrput(),可以打断加锁
---todo
.公平锁new ReentrantLock(true);
谁等待在前面,谁先执行。而不是一起抢。就是排不排队
先到得线程在等待队列中,公平锁:新来的线程会查看队列中是否有线程,如果有,进入队列等待;非公平锁:新来的线程直接抢锁是可能抢到得。
不是绝对的公平,只是先取队列中的。你还没排队,我买完东西后,又排队了。
.ReentrantLock vs sync锁的类型·
trylock
lockInterruptibly
公平和非公平
.CountDownLatch倒数的门栓:等待结束
await();//阻塞
countDown();//-1,本身是原子的
也可以使用join
.CyclicBarrier循环栅栏:
CyclicBarrier cb = new CyclicBarrier (20,new Runnable(){
run(){
满20人,发车
}})
CyclicBarrier cb = new CyclicBarrier (20,()-> );
cb.await();//加人
读取数据场景
数据库,网络,文件必须都读到才能执行,
顺序执行太慢,并发执行并使用CyclicBarrier大大节约执行时间。
使用时间比较:1+2+3 1,2,3里最长的
.Guava RateLimiter场景:限流
.MarriagePhaser 1.7arriveAndAwaitAdvance();//等待到达
arriveAndAwaitderegistert();//阶段停止
按不同的阶段执行:分阶段执行,需要多个参与的。(遗传算法)
循环栅栏,多个关卡,每个关卡的线程数。
场景:结婚 到达,吃,离开,洞房
不同的人有不同的阶段
所有人到才能开席,新浪新娘才能洞房
.ReasWiriteLock读(共享)写(排他,互斥)锁
读多,写少,保证数据的一致性。读写都需要加锁,避免脏读。
.Semaphore 信号灯限流,同时只允许多少个线程执行。高速口,5个收费窗口
线程执行去获取锁,才能执行。限制的不是多少线程,而是限制多少线程同时执行。
公平,非公平
.Exchange交换器,两个线程交换数据使用。阻塞交换,继续执行。
多个阻塞再两两交换。
游戏中交换装备,DNF交易
乐关锁:aqs,悲观锁:sync ,自旋锁:aqs,读写锁(排它锁,共享锁):lock,分段锁:ConcurrentHashMap,Segment



