网教网

搜索
查看: 88|回复: 0

《Java并发编程的艺术》学习笔记(一)

[复制链接]

4

主题

5

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 2023-4-10 18:50:06 | 显示全部楼层 |阅读模式
"这本书感觉并不是那么适合初学者,阅读顺序应该要有所调整,先大概记录记录,后面再写文章进行填坑。"

  • 第一章:并发编程的挑战

    • 上下文切换是什么以及问题

      • CPU通过时间片分配来循环执行任务,当前任务执行一个时间片以后会切换到下一个任务。
      • 切换前会保存上一个任务的状态,以便下次切回这个任务。上下文切换就是任务从保存到再加载的一个过程。
      • 就好比读CSAPP这本书的时候有遇到不认识的单词去查单词,大脑先必须记住刚刚已经阅读到哪一页哪一行了,查完单词以后继续阅读,这样子频繁切换是会极大地影响阅读效率。

    • 如何减少上下文切换

      • 无锁并发编程:避免使用锁。
      • CAS:CAS是不需要加锁的。
      • 使用最少线程:避免创建不需要的线程,任务少但是创建了很多线程来处理,会造成大量线程等待。
      • 使用协程:在单线程里面实现多任务调度,并在单线程里维持多个任务之间的切换。

    • 死锁

      • 避免死锁的方法:①尝试使用定时锁、②对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

    • 资源限制的挑战

      • 是什么?在进行并发编程时,程序的执行速度受限于计算机的硬件or软件资源。硬件限制:带宽的上传/下载速度、硬盘读写速度;软件限制:数据库的连接数,socket连接数。
      • 对于硬件资源限制:使用集群并行执行程序。对于软件资源限制:使用资源池将资源复用。


  • 第二章:Java并发机制的底层实现原理

    • volatile的应用

      • volatile是什么?是轻量级的synchronized,可以保证变量的可见性和有序性,但是不能保证原子性。

        • 可见性:一个线程修改一个共享变量的时候另外一个线程能读到这个修改的值。
        • 有序性:不会对有数据依赖的指令进行重排序(没有依赖的随便排)。

      • volatile与MESI

        • 一个处理器的缓存会回写到内存会导致其他处理器的缓存无效。使用MESI协议去维护内部缓存和其他处理器缓存的一致性。处理器使用嗅探技术保证数据在总线上保持一致。
        • 每个处理器通过嗅探在总线上传播的数据来检查来自己缓存的值是否过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
        • MESI:modified(修改)、exclusive(独享)、shared(共享)、invalid(无效)。CPU写数据时发现写的变量是共享变量的话系统会发出信号通知其他CPU将该内存变量的缓存行设置为无效,当CPU读取的时候发现该变量的缓存行是无效的就会从主存中读取最新的值。

      • volatile的优化(没看懂,待补充。。。)

    • synchronized的实现原理与应用

      • synchronized实现同步的基础:Java中每一个对象都可以作为锁。

        • 对于普通同步方法锁的是当前实例对象(this)
        • 对于静态同步方法锁的是当前类的class对象
        • 对于同步方法块锁的是指定的对象

      • synchronized在JVM里的实现原理

        • JVM基于进入和退出monitor对象来实现方法同步和代码块同步。
        • monitorenter在编译后插入到同步代码块的开始位置,monitorexit插入到方法结束处和异常处。JVM要保证每个monitorenter必须有对应的monitorexit与之配对。

      • Java对象和java对象头

        • synchronized用的锁是存在Java对象头里的。Java对象包括三部分:对象头、实例数据、对齐填充。而Java对象头又包括三部分:MarkWord、对象指针(用来表明这个是哪个类的实例对象)、数组长度(数组才特有的)。
        • Java对象头里的Mark Word默认存储对象的hash code、分代年龄和锁标记位。
        • MarkWord

          • 一共32bit,最后两位表示锁(锁标志位),倒数第三位表示是否是偏向锁。
          • 锁标志位:10,01和00。00表示轻量级锁,01有两种情况:倒数第三位(偏向锁标志)是1的话表示偏向锁,是0的话表示无锁,10表示重量级锁。


      • 锁的升级

        • 偏向锁

          • 为什么要有偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了降低代价就引入了偏向锁。
          • 一个线程访问同步块获取锁的时候,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID。以后该线程在进入和退出同步块时不需要CAS来加锁和解锁,只需要check一下对象头里的MarkWord是否存储着指向当前线程的偏向锁。
          • 偏向锁的撤销:用完以后不会将MarkWord中的信息改回去,就是说不会主动释放锁。有3种情况:①线程B首先判断A是否存活,A挂了就重偏向,将线程ID设置为自己的。②若A没挂但是synchronized代码块执行完了也可以重偏向。③如果这个时候A没执行完B来竞争了就锁升级到轻量级锁。
          • 小总结:MarkWord最后三位是001的时候表示无锁,线程A来了自己加锁,加的是偏向锁。A只需要在第一次加锁的时候进行CAS操作将倒数第三位(偏向锁标志位)设置为1就行,并将自己的线程ID写入到MarkWord和栈帧中的锁记录里。之后A每次来都不用获取锁了。但是如果出现B来竞争就有多种情况,因为A执行完了不会主动释放锁。三种情况就是上面第三点。

        • 轻量级锁

          • 轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将MarkWord复制到锁记录中(备份)。线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针,成功的话就获取锁成功。
          • 轻量级锁解锁:使用CAS操作将锁记录中的MarkWord替换到对象头。成功则表示竞争没有发生,失败的话表示存在竞争,升级成重量级锁。
          • 小总结:轻量级锁加锁之前会先创建一个锁记录,同时将MarkWord备份到锁记录中。锁记录放在加锁线程的虚拟机栈中,MarkWord指向哪个线程的虚拟机栈中就是哪个线程获得了锁。AB同时竞争锁,两个都会创建锁记录放入自己的虚拟机栈中,同时执行CAS在MarkWord中设置自己的锁记录的地址,谁成功就谁获取锁。




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表