1、并发编程的挑战

多线程并不一定能带来性能提升,相反过多的线程导致线程创建和上下文切换有时会比单线程性能更低

无锁并发编程:根据数据id进行取模,不同的线程处理不同段的数据

死锁:资源互相等待,线程因为一些异常没有释放锁

避免死锁:避免一个线程同时获取多个锁、lock.tryLock(timeOut)、避免一个线程在锁内获取多个资源、数据库加解锁必须在同一个数据库连接里

2、java并发机制的底层实现原理

volatile:volatile是轻量级的sychronized,使用恰当的话他比sychronized的使用成本更低

如何实现可见性:多核处理器下,当对含有volatile的变量进行操作,jvm会向处理器发送命令,将这个变量写回主存,然后其他cpu通过嗅探在总线上的数据发现自己的这个变量已经失效,那么下次操作的时候就会从主存读取。

synchronized原理:

通过持有monitor对象,执行monitorenter和monitorexit,monitorenter放在方法入口处,monitorexit放在方法结束和异常处。当一个monitor被持有后,他处于锁定状态、

Java对象头:

mark word:锁标志位、hashcode、分代年龄

类信息地址指针

array lenth:数组长度

锁升级与对比:

为什么不能锁降级:为了提高获取锁和释放锁的效率

1.偏向锁:hotspot作者研究发现大部分情况下,锁不存在多线程竞争,并且总是由同一个线程获取,因此引入偏向锁,当一个线程拿到锁后,对象头markword会记录该线程id,下次进入的时候只需简单对比markword中的线程id,如果是当前线程,则获取锁成功,否则判断对象头是否开启了偏向锁,如果开启了则尝试将线程id指向当前线程

偏向锁的撤销:当有其他线程想进行CAS替换markword信息,则开始撤销偏向锁,等到全局安全点,把markword的线程id置位空

偏向锁默认开启,可通过jvm参数关闭

2.轻量级锁

线程执行同步块之前,jvm先将markword中的锁信息拷贝到栈帧,当线程执行完后,尝试CAS将锁记录从栈帧拷贝回markword,如果此时存在其他线程尝试获取锁,则膨胀为重量级锁

原子操作的实现原理:

处理器如何实现:总线锁和缓存锁,总线锁开销大,缓存锁是缓存变量所在主存中的地址,当cpu1锁定了某个缓存行,那cpu2就不能同时缓存该行

Java如何实现:锁或CAS

CAS三大问题:ABA、循环开销大、只能操作一个变量

ABA问题:版本号->AtomicStampReference

循环开销大:如果jvm支持CPU的pause指令,那么效率上会有一定提升,pause可以使cpu流水线延迟执行指令

只能操作单变量:可以吧多个变量放在一个对象,使用AtomicRefrence

3.Java内存模型

java通过内存模型来控制线程间通信,线程间通信步骤:1.线程A将变量刷新到主存2.线程B将主存中的变量拿到自己的内存中。

为了防止指令重排引起的线程安全问题,jvm通过在指令间增加内存屏障防止指令重排

happens-before原则:定义了一些进程、线程、锁相关的执行顺序。A happens-beforeB,B happens-beforeC,那么A happens-before C,对于一个监视器的解锁,happens-before于对监视器的加锁

as-if-serial语义:不管怎么排序,单线程程序的执行结果不能被改变。

volatile内存语义:保证单个变量读写的原子性,通过在读写操作之间加入内存屏障

锁的内存语义:线程A释放锁时,会将变量写入主存,然后通过主存发送消息给B,当B获取锁后,JMM会把本地变量失效,然后到主存中读取共享变量。

ReentrantLock使用AQS进行同步,volatile修饰的state变量,公平锁和非公平锁释放时,最后都要写state变量,公平锁获取时,先读state变量,非公平锁获取时,先CAS修改state变量

final域的内存语义:禁止吧final域的写重排序到构造函数外,确保在读到final域前是已经初始化完成的

happens-before的两类重排序:对于会更改程序结果的重排序,JMM不允许编译器和处理器进行重排序,对于不会更改程序结果的重排序,JMM不做要求

双重检查锁定与延迟初始化:双重检锁单例模式中,instance要加volatile修饰,否则会因为重排序出现线程安全问题,还有一种Holder单例模式,通过private static修饰holder 类和instance,可以保证延迟加载和线程安全。

延迟初始化降低了初始化创建实例的开销,但是却增加了访问的开销,大多数时候,正常初始化优于延迟初始化。

4.Java并发编程基础

线程优先级:priority,可以设置1-10,但是有些操作系统会忽略优先级设置

线程状态:new:刚创建还未start(),runnable:java将操作系统中就绪和运行统称为runnable,blocked,wait,time_wait,teminated

注意:在synchronized前的线程是BLOCKED状态,在Lock接口前的线程是WAITED状态,因为Lock接口的阻塞使用了LockSupport中的方法

线程暂停方法:suspend(),resume(),stop(),但是这些方法是过期的,因为可能会导致死锁或者线程工作在不确定状态下的问题,建议使用等待/唤醒机制。

等待/唤醒流程:线程A拿到锁,调用wait方法进入WaitQueue,此时是WAIT状态,然后线程B拿到锁,调用notify方法,此时A从WaitQueue进入SynchronizedQueue,BLOCKED状态,线程B释放锁后,线程A拿到锁并继续执行。

ThreadLocal:以threadLocal对象为key,任意对象为值的存储结构。

5.Java中的锁

Lock相比synchronized,需要手动获取锁和释放锁,并且可以设置锁的释放时间,注意获取锁要在try块之前,否则获取锁异常后,异常抛出时也会使得锁失效

AQS:队列同步器,由FIFO队列和volatile state组成

同步队列:FIFO双向列表,当一个线程获取锁失败,则被构造成节点使用CAS加入同步队列尾部,并自旋。可以用acquire获取同步状态,该方法对中断不敏感,也就是当线程处于同步队列时,即使中断了也不会移出队列。

独占式state获取和释放:移出队列的条件时前驱节点称为头结点且成功获取到同步状态,在释放同步状态时,会唤醒后继节点。

共享式state获取和释放:成功获取同步状态并退出自旋的条件是tryAcquireShard方法返回值大于0,当释放同步状态后,会唤醒后续处于等待状态的节点。

独占式超时state获取和释放:和不超时的主要区别是,线程超过了设置的nanoTime后,自动返回

重入锁:ReentrantLock中,获取state值前会判断线程,如果重新获取state的线程是之前的线程,则state+1,解锁之后state-1

公平锁与非公平锁:ReentrantLock之所以默认非公平锁,是因为同个线程重新获取state的几率非常大,这样就防止了同步队列中线程频繁更换,导致频繁上下文切换,但是非公平锁也会造成线程饥饿。

读写锁的实现:在ReentrantLock中,state用来表示一个线程获取锁的次数,在读写锁中,这个变量要维护多个读进程和一个写进程的状态,因此需要按位切割使用该变量,高16位是读,低16位是写

写锁的获取和释放:写锁是一个可重入的排它锁,释放和ReentrantLock一样,也是减少state值。

读锁的获取和释放:读锁是一个可重入的共享锁,每次被获取就增加读状态,释放就减少读状态

锁降级:一个线程先获取写锁,然后获取读锁,最后释放写锁的过程。这个过程是为了保证数据的可见性。

LockSupport工具:LoclSuppoet提供了阻塞和唤醒功能,java6中在原有的park(),park(nanos)上增加了park(Obkject blocker),park(Obkect brocker,long nanos),这个参数便于dump打印出更详细的对象信息,而java5的park()是无法提供对象信息的。

Condition接口:任何java对象都有一组监视器方法,wait(),notify(),notifyAll(),可以与synchronized搭配实现等待/通知模式,Condition接口也提供了一些列方法和Lock实现等待/通知模式。Condition是AQS的一个内部类

6.Java并发容器和框架

ConcurrentHashMap:使用segment数组和hashentry数组组成,segmentShift和segmentMask需要在定位segment的hash算法上使用,

定位segment:将key进行再散列,目的是为了元素更平均的分布在segment上,减少hash冲突

get:先经过一次再散列,然后用这个散列值进行散列定位到segment,然后在通过散列算法定位到元素,get是不加锁的,原因是他会对要使用的元素用volatile修饰,在happens-before语义中,保证了同一时刻进行读写,get也能拿到最新值。

put:先定位到segment,然后加锁,插入前先判断是否需要扩容,如果需要扩容,则将segment扩容2倍,然后将旧值进行再散列。值得注意的是,segment的扩容比hashmap的扩容更切当,因为hashmap是新增元素后才判断需要扩容,如果新增完后没有再put元素,此次就是无效扩容,而且concurrentHashMap不是整个容器进行扩容,而是单个segment进行扩容

size:使用一个volatile修饰的count维护,如果在计算大小过程中元素数量发生改变,则用一个modeCount记录。

7.Java中13个原子操作类

AtomicInteger实现原理:使用了unsafe类的compareAndSwapInt方法,如果是符合预期的,则修改,否则自旋重试,

8.Java中的并发工具类

countDownLatch、cyclicBarrier区别:cyclicBarrier可以用reset方法进行重置,可以用isBroken方法判断元素是否被中断,可以用getNumberWaitng方法判断阻塞的线程数

semapphore:线程数控制

Exchanger:用于线程间数据交换,当两个线程到达同步点,可以交换数据、

9.Java中的线程池

处理流程:核心线程->队列->最大线程->拒绝策略

如果当前线程数小于corePoolSize,则创建线程,当时创建核心线程需要获取全局锁,所以最好是先预热核心线程数

线程池会将线程封装成worker,而worker会不断地从队列中获取任务执行

关闭线程池:shutdown和shutdownNow,原理是遍历工作线程调用interrupt()方法,shutdown()是将线程池设置为SHUTDOWNz状态,然后中断没有正在执行任务的线程,shutdownNow()是将线程池设置为STOP状态,然后将所有进行中或没有任务的线程都中断。

合理配置线程池:依赖数据库的线程池,可以将线程数设置大,可以减少等待数据库返回数据时CPU空转时间。建议使用有界队列,否则容易造成内存溢出

10.Executor框架

Execotor接口下有ThreadPoolExecutor、ScheduledThreadPoolExecutor

SingleThreadPoolExecutor:只有一个工作线程,适合用来需要顺序执行任务的场景

FixThreadPoolExecutor:固定线程数

CachedThreadPoolExecutor:无固定大小

ScheduledThreadPoolEeecutor:定时

Future:保存异步任务结果

Runnable:不返回结果

Callable:返回结果

JAVA并发编程的艺术-读书笔记相关推荐

  1. 《Java并发编程的艺术》笔记

    <Java并发编程的艺术>笔记 第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种 ...

  2. 《Java 并发编程实战》--读书笔记

    Java 并发编程实战 注: 极客时间<Java 并发编程实战>–读书笔记 GitHub:https://github.com/ByrsH/Reading-notes/blob/maste ...

  3. Java并发编程的艺术-阅读笔记和思维导图

    最近在坚持每天阅读<<Java并发编程的艺术>>,不但做好笔记(MarkDown格式),还做好思维导图. 如果大家感兴趣,可以可以到码云上阅读笔记和到ProcessOn上阅读思 ...

  4. 并发编程的艺术 读书笔记

    第一章 并发编程的挑战 1. 单核CPU分配运行时间给各个线程,实现多线程执行代码. 举例:看英文书时某个单词不会,先记住看到书的页数和行数,然后去查单词,查完回到看书状态,相当于上下文切换. 2. ...

  5. [转] 《Java并发编程的艺术》笔记

    转自https://gitee.com/Corvey/note 作者:Corvey 第一章 并发编程的挑战 略 第二章 Java并发机制的底层实现原理 volatile的两条实现原则: Lock前缀指 ...

  6. 《Java并发编程实践》读书笔记

    http://macrochen.iteye.com/blog/660796 并发编程在编写高性能, 可伸缩应用的时候经常用到的一项技术, 也是相对来说比较高级的一项技术, 是每一个做后端开发的必备技 ...

  7. 《Java并发编程实战》读书笔记

    Subsections  线程安全(Thread safety) 锁(lock) 共享对象 对象组合 基础构建模块 任务执行 取消和关闭 线程池的使用 性能与可伸缩性 并发程序的测试 显示锁 原子变量 ...

  8. 竞态条件的赋值_《Java并发编程实战》读书笔记一:基础知识

    一.线程安全性 一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步,这个 ...

  9. java任何封闭实例都不是java_《java并发编程实战》读书笔记3--对象的组合

    希望将一些现有的线程安全组件组合为更大规模的组件或程序 设计线程安全的类 如果对象中所有的域是基本类型变量,那么这些域将构成对象的全部状态.例如,LinkedList的状态就包括该链表中所有节点对象的 ...

最新文章

  1. 我是如何学习写一个操作系统(二):操作系统的启动之Bootloader
  2. SQL语句大全(转)
  3. Strust2用户注册,使用token防止刷新重复提交
  4. margin系列之负值
  5. Go 语言编译过程概述
  6. Linux桌面图形化安装详解
  7. adb指令禁用软件_免Root使用ADB命令_停用手机系统应用
  8. RESTClient用法
  9. 鸿蒙系统和全屋智能,不断升级“常用常新” 搭载鸿蒙系统的全屋智能有多酷?...
  10. 成本要素****没有被分配到成本组件结构01中的成本组件
  11. 服务器bmc口装系统,IBM X3650服务器BMC安装系统
  12. 软件架构——系统分析员、系统架构师、项目经理的区别
  13. 计算机计算建筑结构的方法有哪些,建筑结构设计包括哪些内容呢?
  14. php实现emoji表情包的数据库存储和展示
  15. Jetty修改默认端口
  16. 数据通信基础 - 解调技术(PCM)
  17. Java面试题集(第七部分)(151-180)
  18. Ipopt开发环境安装
  19. html画等边三角形,CSS做等边三角形
  20. 一台服务器同时配置内外网地址

热门文章

  1. 博途PLC和CODESYS平台下FB编程应用(如何实例化多个FB)
  2. 新版标准日本语中级_第十一课
  3. 如何正确卸载Oracle避免卸载Oracle不干净解决一
  4. 可兼容所有浏览器的“收藏本站”、“设为首页”js代码
  5. Linux中如何新建用户
  6. 摄像头识别黑烟加框处理(测试)
  7. 2、二叉树的后序遍历
  8. preempt-RT patches
  9. 自建低成本代码托管与文档管理平台经验分享
  10. 试题 历届试题 青蛙跳杯子