前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发总篇

Java并发总篇

作者头像
None_Ling
发布2020-09-08 22:05:11
5580
发布2020-09-08 22:05:11
举报
文章被收录于专栏:Android相关Android相关

AQS - AbstractQueuedSynchronizer

AQS是并发基类 , 通过State以及Exclussive Thread来控制资源总数以及资源独占的线程. 通过LockSupport.park/unpark来控制线程CPU的调度 , 用于让某个线程获取/让出CPU资源.

AQS主要分为两种模式 :

  • 独占模式(Exclussive Mode) : 只有1个资源 , 同一时刻只允许一个线程执行
    • 包括 : ReentrantLock
  • 共享模式(Shared Mode) : 拥有多个资源 , 只要资源数足够 , 同一时刻允许多个线程获取资源执行
    • 包括 : Semaphore、CountDownLatch、CyclicBarrier

LinkedBlockingQueue

BlockingQueue主要用于实现生产者-消费者模型的实例.

主要通过ReentrantLockCondition来实现生产与消费. 其内部会创建两个ReentantLock :

  • takeLock : 负责生成notEmpty的Condition , 用于阻塞take函数获取数据
  • putLock : 负责生成notFull的Condition , 用于阻塞put函数插入数据

内部也有capacity变量用于标志当前队列总长度 , 也有AtomicInteger count来标志当前队列长度.

put函数插入数据 , 而队列满了的时候 , 会通过notFull.await让插入线程等待. 而当take函数获取完数据 , 队列不满的时候 , 会通过notFull.signal唤醒线程进行插入.

take函数获取数据, 而队列长度为0的时候 , 会通过notEmpty.await等待数据插入. 而当put函数调用后 , 队列中有数据时 , 会通过notEmpty.signal唤醒线程获取数据.

CAS操作

CAS(Compare and Swap),即比较并交换。比较的是当前内存值与预期值,交换的是新值与内存中的值。这个操作是通过cmpxchg指令来完成 , 但在该指令前 , 还有一个lock前缀 , 而这个lock前缀才是保证多核CPU的关键.

代码语言:javascript
复制
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    // 如果是多核CPU , 则lock
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

而在上层 , Unsafe会通过自旋的方式一直等待cmpxchg执行成功后返回结果.

代码语言:javascript
复制
public final int getAndAddInt(Object o, long offset, int delta) {
     int v;
      do {
            v = getIntVolatile(o, offset);
      } while (!compareAndSwapInt(o, offset, v, v + delta));
     return v;
   }

volatile关键字实现

volatile关键字主要有两个功能 :

  • 线程间可见性 : 保证该变量的值在各个线程间都是获取最新的
  • 阻止指令重排序 : 保证在volatile前的指令可以执行完毕

class文件中 , 仅仅只是加上了ACC_VOLATILE的Flag

在虚拟机中 , 会通过lock前缀 , 来标志该条指令.

lock指令用于在多处理器中执行指令时对共享内存的独占使用。它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。

synchronized关键字实现

在jdk1.5之前 , synchronized关键字的性能非常差 , 因为在JVM中遇到synchronized关键字就会使用Linux的Mutex进行并发.

在jdk1.6版本后 , 通过对锁对优化 , synchronized的性能已经达到一个很优秀的程度. 通过在JVM加入了偏向锁轻量级锁重量级锁的策略.

Java中锁膨胀的顺序为 : 无锁 , 偏向锁 , 轻量级锁 , 重量级锁

  1. 偏向锁 :
  • 检查对象头Mark Word中是否保存有线程ID , 如果有则认为当前锁处于偏向锁
  • 如果没有则通过CAS设置对象头中的线程ID , 如果成功 , 则代表从无锁成为偏向锁
  • 如果CAS失败或者已经存在线程ID , 则当到线程安全时 , 会撤销偏向锁 , 升级成为轻量级锁
  1. 轻量级锁 : 多个线程竞争偏向锁导致偏向锁升级为轻量级锁
  • JVM 在当前线程的栈帧中创建 Lock Reocrd,并将对象头中的 Mark Word 复制到 Lock Reocrd 中。(Displaced Mark Word)
  • 线程尝试使用 CAS 将对象头中的 Mark Word替换为指向 Lock Reocrd 的指针。
  • 如果成功则获得锁,如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。
  1. 重量级锁 : 在重量级锁中没有竞争到锁的对象会park被挂起,退出同步块时unpark唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。

synchronized 关键字及 wait、notify、notifyAll 这三个方法都是通过MonitorEnterMonitorExit来实现的.

等待队列处理策略

LockSupport实现

在JVM中 , 会通过Parker来完成park/unpark. 实际上 , 对应在Linux上的实现则也是通过mutex来实现的:

  • park : pthread_cond_wait 以及 pthread_mutex_lock 实现等待
  • unpark : pthread_cond_signal 以及pthread_mutex_lock 实现唤醒

缓存一致性

对于当代多核CPU而言 , CPU会有缓存一致性协议 (MESI) , 主要用于标示CPU Cache Line中缓存的状态 , 并且用于同步多个CPU中缓存状态 .

而在CPU指令中Lock指令用来同步各个CPU核之间的缓存数据.

乐观锁/悲观锁

乐观锁 : 当并发量级很小, 冲突小时 , 使用乐观锁 , 比如volatileCAS操作 悲观锁 : 当并发量级比较大, 冲突较大时 , 使用悲观锁 , 比如ReentrantLocksynchronized , 主要因为CAS自旋多次后 , 会导致CPU大量的空旋 , 浪费cpu资源.

参考资料

[并发系列-7] CAS的大致成本 synchronized 实现原理

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AQS - AbstractQueuedSynchronizer
  • LinkedBlockingQueue
  • CAS操作
  • volatile关键字实现
  • synchronized关键字实现
  • LockSupport实现
  • 缓存一致性
  • 乐观锁/悲观锁
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档