您的位置 首页 java

⑤ JAVA 多线程与高并发

1. Synchronize为什么是重量级的

2. 如何将Synchronized降为轻量级(锁只能够升级不会降级)

3. 什么是CAS

4. CAS的ABA问题如何解决

5. Synchronized与ReenTrantLock的底层实现及重入的底层原理,异同点

6. 锁的四种状态与升级过程

7. 谈一下AQS,AQS的底层为什么是CAS+volatile

8. volatile的理解

9. 描述对象的创建过程(半初始化操作)

10. DCL单例为什么要加volatile

11. Object o = new Object() 在内存中占用多少个字节

12. as-if-serial与happens-before的语义理解

13. ThreadLocal如何解决内存泄露的

14. 锁的分类与在JDK中的应用

15. 自旋锁一定比重量级锁效率高么

16. 打开偏向锁是否效率一定会提升

17. atomic的底层原理

18. 锁重入与锁解锁 —支持重写

1. Synchronized 为什么是重量级锁

  • 重量级锁:申请锁资源需要经过操作系统调用,需要经过操作系统内核kernel

3. 什么是CAS

CAS是一种锁的操作,是一种乐观锁。

CAS:compare and swap

CAS有三个参数:CAS(E,V,N) E需要更新的对象,V对象的原值,N需要更换后的值。

操作的时候,只有当E的值与V相等的时候,才会将E的值更新为N,否则就不执行更新操作。

在多线程的情况下,当返回失败的时候会继续调用CAS方法,直到成功。

无锁或者自旋锁。

CAS的问题:

  • (1)会产生ABA问题:读取值的时候为A,更新的时候还是A,但是中间A可能改完B,然后B又被改成A。
  • 解决方法:加上版本号。AtomicStampedReference/AtomicMarkableReference方法。
  • (2)高并发情况一直循环,大量消耗CPU。

在执行compareAndSwapInt方法的时候,调用的是底层的汇编指令 lock cmpxchg 锁定系统总线。

4. CAS的ABA问题如何解决

  • 给对象加上版本号,每次比较的时候带上版本号进行比较

5. Synchronized与ReenTrantLock的底层实现及重入的底层原理,异同点

5.1 ReenTrantLock是通过CAS与AQS队列来实现的。

  • 1. ReenTrantLock支持公平锁与非公平锁,默认为非公平锁,通过new ReenTrantLock(true)参数为true来实例化公平锁。
  • 2. ReenTrantLock通过lock()与unlock()方法来进行加、解锁
  • 3. ReenTrantLock是一个可重入锁
  1. lock方法调用了compareAndSetState(int 0,int 1)方法【非公平锁有这样步骤】,底层是Unsafe类型的compareAndSwapInt方法,CAS自旋(底层是汇编的Lock CMPXCHG指令),
  2. 如果compareAndSetState方法成功,则获取锁信息,设置独占锁的信息为当前的线程。
  3. 如果没有获取锁,都会去调用acquire(1)【AQS-AbstractQueueSynchronized】方法(源码在AQS视图里面),要么获取锁,要么加入CHL队列进行等待。
  • unlock()方法 调用的是AQS的release方法(源码在AQS视图里面)
  • lockInterruptibly()调用的是AQS的AcquireInterruptibly(int arg)方法,响应中断获取锁方式

5.2 Synchronized (JDK1.6版本优化)

JVM级别的锁,通过monitor对象来进行锁操作的。monitorenter和monitorexit

1. Synchronized修饰的对象不同

  • 1.1 Synchronized修饰在实例方法的时候 public synchronized void test(){}, 反编译后可以看见方法上提交了ACC_SYNCHRONIZED修饰符,JVM如果扫描到存在ACC_SYNCHRONIZED修饰符会调用Monitor对象,对方法添加锁信息,方法执行结束后会调用monitorexit释放锁。
  • 1.2 Synchronized修饰在类成员方法(静态方法)的时候,public synchronized static void test(){} 反编译也会存在ACC_SYNCHRONIZED修饰符,和上面情况一样。
  • 1.3 Synchronized修饰在对象上的时候, synchronized(this){},反编译后会在方法前后加上monitorenter与monitorexit指令。
  • 1.1.1 monitorenter指令表示获取锁对象的monitor对象,执行monitorenter指令的时候会在monitor对象里面的count进行加一操作,执行monitorexit会对count进行减一操作。

Synchronized锁的信息保存在对象的头信息的Mark Word的后两位里面。结构存在“多线程与高并发(锁)”里面。

差异:

  • 1. 底层实现:
  • Synchronized是JVM层级的锁,是JAVA的关键字,通过monitor(monitorenter、monitorexit)指令来进行锁的操作。只有在同步代码块中才能够调用wait与notify方法;
  • ReenTrantLock是JDK1.5以来提供的API层级的锁。
  • 2.是否可以中断
  • Synchronized是不可中断的锁类型,除非执行过程中报异常或者正常执行结束。
  • ReenTrantLock是可以中断的锁,使用tryAcquire(int arg,TimeOut time)超时中断或者acquireInterruptibly()方法调用interrupt中断。
  • 3.是否需要手动释放
  • Synchronized是可以在代码执行结束后自己释放锁的,不需要手动释放
  • ReenTrantLock的加锁与解锁需要手动控制,通过lock()、unlock()方法在try\finally代码块中执行,如果不手动释放锁会导致死锁产生。
  • 4.是否公平锁
  • Synchronized是非公平锁
  • ReenTrantLock默认是非公平锁,可以在初始化的时候通过构造函数new ReenTrantLock(flag)来初始化锁是否为公平或非公平,true为公平锁,false为非公平锁
  • 5. 锁上是否可以绑定条件condition
  • Synchronized不能够绑定condition,Synchronized只能够通过Object类的wait()/notify()/notifyAll()方法来随机唤醒一个或者唤醒所有线程;
  • ReenTrantLock可以通过Condition类来绑定condition,通过await()/signal()方法来精确唤醒线程
  • 6.锁的对象
  • Synchronized锁的是对象,锁信息保存在对象头的Mark Word里面
  • ReenTrantLock锁的是线程,线程的信息存储在CHL队列的Node节点里面

6. 锁的四种状态与升级过程

状态:

  • 1. 无锁
  • 2. 偏向锁
  • 3. 轻量级锁(自旋锁)
  • 4. 重量级锁

锁升级的过程:

  • 1. 线程A在进入同步代码块前,先检查Mark Word中的线程ID是否为A的ID,如果为A的ID,这A获取该锁,否则判断这个锁是否为偏向锁。
  • 2. 如果这个锁不是偏向锁,则A进行自转,等待锁的释放。
  • 3.如果锁为偏向锁,判断该线程是否存在(偏向锁不会主动释放锁),如果不存在,则把线程ID改成A的ID。
  • 4. 如果存在,则暂停该线程,并将锁的标志位升级为00即轻量级锁(将Mark Word中信息复制到该线程的栈帧中,同时在Mark Work设置栈帧中的锁记录
  • Lock record指针),线程A在外自旋等待锁的释放。
  • 5. 当线程A自旋次数到达设定的自旋上限(默认10),线程锁还没有释放,或者A在自旋过程中,又有其它的线程过来竞争这个锁对象(竞争加剧),这个时候会将轻量级锁升级为重量级锁,外部的线程都进入等待队列,不再自旋抢锁,当锁释放后,会唤醒所有的等待线程,让他们重新竞争锁。
  • 注:JDK1.6后,轻量级锁升级为重量级锁是JVM控制的,自适应操作

8. volatile的理解

不能够保证程序的原子性,Synchronized可以

  • 1)指令重排序
  • 2)程序可见性

缓存一致性协议 MESI

  • 1. 缓存一致性 volatile修饰
  • 1.1 当线程修改一个共享变量后,会立刻将修改的内容刷新到主存中,确保主存中的数据永远是最新的。
  • 1.2 当前线程修改某一共享变量后,系统会通知所有使用该变量的线程,该对象状态已经为无效状态了,需要重新去主存中获取最新的内容,再次读取到工作缓存中

9. 描述对象的创建过程(半初始化操作)

int a=8;

  • 1)分配内存空间 a=0
  • 2)初始化对象 a=8
  • 3)将内存空间的地址赋值给对象的引用 a=8

10. DCL单例为什么要加volatile

  • 在对象的初始化过程中,如果不加volatile参数,上面的第2与3步可能会发送指令重排序操作,这个时候变量的值就有可能为a=0

12. as-if-serial与happens-before的语义理解

  • as-if-serial 不管如何重排序,单线程执行的结果不会改变
  • happens-before JVM重排序规则 8种 4种内存屏障 LL SS LS SL

17. atomic的底层实现原理

atomic:底层是通过自旋+CAS来实现的,CAS解决并发原子性操作。

  • 1.调用Unsafe类,直接访问对象的内存
  • 2.使用getIntVolatile(var1,var2)方法,获取线程间共享变量
  • 3.使用CAS尝试机制,进行更新数据
  • 4. 使用Atomic是在硬件和寄存器上进行阻塞,而不是代码、线程

do..while..自旋代码在高并发的情况下会加大执行次数,消耗CPU,在低并发的情况下比较有优势,因为不进入内核态,会提高一定的性能。

 Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object var1,long var2,int var4){
  int var5;
  do{
   var5 = this.getIntVolatile(var1,var2);
   //使用unsafe的native方法,实现高效的硬件级别CAS
  }while(!this.compareAndSwapInt(var1,var2,var5,var4));
  
  return var5;
 }  

补充:线程基本概念

Synchronized

volatile

AtomicXXX

各种JUC的同步锁

  • ReenTrantLock 排它锁、公平锁
  • CountDownLatch 门栓 latch.await()阻塞,countDown()为0的时候才会执行后面方法
  • CyclicBarrier 栅栏,满多少个线程后,执行CyclicBarrier里面定义的线程
  • Phaser 分段执行线程
  • ReadWriteLock 读写锁
  • ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Lock readLock = readWriteLock.readLock();
    Lock writeLock = readWriteLock.writeLock();
  • Semaphore 限流,限制只允许获取acqurie()成功的线程执行
  • Exchanger exchange()交换线程引用
  • LockSupport unpark() park() 较灵活的暂停与启动

文章来源:智云一二三科技

文章标题:⑤ JAVA 多线程与高并发

文章地址:https://www.zhihuclub.com/171977.shtml

关于作者: 智云科技

热门文章

网站地图