个人梳理总结:
一.synchornized原理
1. synchornized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现临界资源的同步互斥访问,是可重入的.
2.synchornized属于隐式锁,是jvm内置锁,jvm会自动加锁跟解锁,由于他是对象锁,所以他不够灵活,不能跨方法加锁,
如果要实现跨方法加锁,有两种方式:
1.ReentrantLock(显示锁)进行手动加锁和手动解锁
2.使用魔术类unsafe直接越过jvm,去操作底层对对象进行手动加锁和解锁
UnsafeInstance.reflectGetUnsafe().mintorEnter(object) //加锁
UnsafeInstance.reflectGetUnsafe().mintorExit(object) //解锁
3.synchornized底层原理:每个对象在创建的时候都会创建一个与之对应的Moniter对象,基于进入与退出Moniter对象实现方法与代码块同步,Moniter底层就是一个Mutex Lock(互斥锁),它是一个重量级锁,性能低,但是在1.5版本后对其做出了重大优化,如锁粗化、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平
4.每个对象在加锁以后,会在对象的对象头中记录锁的状态,对象信息,锁的状态标志,偏向锁ID,偏向时间,
二.Jvm锁的优化
1.锁的粗化
StringBuffer stb = new StringBuffer();stb.append("桑");
stb.append("翔");
2.锁的消除
synchornized(new Object){
//伪代码:很多逻辑
}
三.JVM内置锁的膨胀升级
下面的过程是不可逆的
1.偏向锁:jvm认为当进入同步块的时候一帮认为只有一个线程,那么锁就会进入偏向模式,当这个线程再次请求锁的时候,无需再做任何同步操作,提高了性能
2.轻量级锁:在锁竞争比较激烈的场合,偏向锁就失效,升级成为了轻量级锁,它的场合一般是线程交替执行同步块的场合(竞争不是很激烈),当第二个线程来竞争锁的时候,不会让其阻塞,而是进行自旋,这样就不会丢弃CPU的使用权,减少唤醒阻塞线程的开销
3.轻量级锁是如何记录当前交替执行的线程的呢?
当锁升级成轻量级锁的时候,线程会在自己的线程栈中开辟一个LockRecord空间,将此对象头中的信息复制一份放入当中,并且将原对象头中的信息指向线程栈中的owner指针
锁定膨胀实际过程:
Mark Word
1.
解析:当一个线程访问同步块的时候,他会先检查同步对象的标志位,就是上图中的锁标志位为01,和是否偏向0
此时就会通过CAS修改成是否偏向1,获取偏向锁,并将MarkWord中的线程ID指向自己
然后执行同步块中的逻辑, 此时如果又有一个线程访问同步块,同样会检查标志位中的信息,并会尝试修改MarkWord中的线程ID,此时会修改失败,当第一个线程运行到一个安全位置以后,若第一个线程没有运行完毕,则会升级为轻量级锁,让第二个线程自旋等待,如果第一个线程执行完毕,则降偏向1改为0,线程id清空;
:
2.
四.ReentrantLock&AQS
1.概念: Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器
实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获
取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,AQS定
义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器
2.应用:Lock,Latch,Barrier,线程池中的阻塞队列等都是基于AQS去实现的
3.AQS框架管理状态:
1. state表示资源的可用状态(用volitel修饰的,保证线程的可见性)
通过CAS操作去原子性的操作state值(底层是unsafe类操作汇编指令)
2. node(CLH同步队列---阻塞)
用来存放没有获取竞争锁的线程,内部有各种标记当前节点的信号量属性,前后继节点等
3.exclusiveOwnerThread,表示当前独占线程是谁------源自AbstractOwnableSynchronizer(AbstractQueuedSynchronizer继承的)
当有一个线程进行加锁的时候:
head和tail都为null,表示CHL队列中没有等待的线程
当有二个线程进行加锁的时候