Java并发笔记

volatile

volatile是轻量级的synchronized,它在多处理中开中保证了共享变量的“可见性”,即:一个线程的写操作的结果可以被另一个线程读到。
volatile不会引起线程上下文的切换和调度,比synchronized使用和执行成本更低。

写操作

jvm会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存,将其他CPU里缓存了该内存地址的数据置为无效。

读操作

发现读到的是无效数据,重新从系统内存中读到处理器缓存中。
JMM把线程本地内存置为无效,从主内存中读取共享变量

synchronized

  1. 对于普通同步方法,锁是当前实例对象
  2. 对于静态同不方法,锁是当前类的Class对象
  3. 对于同步方法块,锁是Synchronized括号里配置的对象

JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,monitorenter插入同步代码块开始位置,monitorexit插入方法结束处和异常处。
任何对象都有一个monitor与之关联。

synchronized用的锁是存在Java对象头中的。

Java对象头:hashcode 分代年龄 是否是偏向所 锁标志位

锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态

原子操作的实现原理

1. 总线锁

使用处理器提供一个LOCK#信号,其他处理器请求被阻塞,那么该处理器就可以独占共享内存

2. 缓存锁

修改内部的内存地址,并允许它的缓存一致性机制
两种情况不使用:
1.不能缓存在CPU
2.CPU不支持缓存锁

3. Java实现原子操作

通过锁和循环CAS实现

Java内存模型

  1. 抽象定义

    1. 共享变量:实例域 静态域 数组元素
    2. JMM定义了线程和主内存之间的抽象关系:
      1. 线程之间的共享变量存储在主内存中
      2. 每个线程都有一个私有的本地内存
      3. 本地内存中存储了该线程以读/写共享变量的副本
Java内存模型的抽象结构示意图.png
  1. 重排序

    1. 编译器优化重排序
    2. 指令级并行重排序
    3. 内存系统重排序

    可能会导致多线程程序出现内存可见性问题,如何解决:

  2. JMM的编译器重排序规则会禁止特定类型的编译器重排序

  3. JMM的处理器重排序规则会插入内存屏障,通过内存屏障来禁止特定类型的处理器重排序。

重排序目的:再不改变程序执行结果的前提下,尽可能提高并行度。

happens-before:前一个操作的结果对后一个操作可见

concurrent包

concurrent包的实现示意图.png

线程

Java线程状态变迁.png

wait/notify

  1. wait() notify() notifyAll() 需要先对调用对象加锁
  2. 调用wait()方法后,线程状态由RUNNING变为WAITING,并把当前线程放置到对象的等待队列
  3. notify()notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  4. notify将线程从等待队列移到同步队列,线程状态由WAITING变为BLOCKED

同步队列

同步队列的基本结构.png
节点加入同步队列.png
首节点的设置.png

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。以文件的读写为例,如果一个程序在对文件进行读操作,那么这一时刻对于该文件的写操作均被阻塞,而读操作能够同时进行。写操作要求对资源的独占式访问,而读操作可以是共享式访问,两种不同的访问模式在同一时刻对文件或资源的访问情况,如下图:

共享式与独占式访问资源的对比.png

ReentrantLock

重入锁:顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。

重进入:

  1. 线程再次获取锁:通过识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
  2. 锁的最终释放:获取多少次锁,最终就需要释放多少次锁,才能成功释放锁。

公平锁和非公平锁:

  1. 公平锁的获取需要判断同步队列中当前节点是否有前驱节点。
  2. 非公平锁的获取只需要CAS设置同步状态成功即可。
  3. 公平锁能够减少“饥饿”发生的概率,保证FIFO,非公平锁效率高。

ReadWriteLock读写锁

  1. 读写锁,维护一对锁:一个读锁和一个写锁,通过分离读写锁,提升了更好的 并发性和吞吐量。
  2. 同一时刻,多个读线程可同时访问读写锁。
  3. 写线程访问时,所有读线程和其他写线程均被阻塞。
  4. Java并发包实现:ReentrantReadWriteLock

LockSupport

LockSupport工具类定义了一组公共静态方法,提供了对线程阻塞和唤醒的操作。
part()阻塞 unpark()唤醒

Condition

Condition定义了等待/通知两种类型的方法,Condition依赖Lock对象,由Lock.newCondition()获取

ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。
并发包中的Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列。

同步队列和等待队列.jpg

Condition的await():将同步队列中的首节点通过addConditionWaiter()方法把当前线程构造成一个新节点并将其加入等待队列。如下图:

当前线程加入等待队列.png

Condition的signal():唤醒等待队列中等待时间最长的节点(首节点)。如下图。

  1. 检查当前线程是否获取了锁:isHeldExclusively()
  2. 将等待队列的首节点移动到同步队列
  3. 使用LockSupport唤醒节点中的线程
节点从等待队列移动到同步队列.png

ConcurrentHashMap

  1. get操作:不加锁,get方法中使用到的共享变量都被定义为volatile类型,除非读到的值时空才会加锁重读。

  2. put操作:

    1. 1.6采用锁住Segment实现并发操作
    2. 1.8采用CAS+synchronized实现并发操作

阻塞队列

JDK 7提供了7个阻塞队列,如下。
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

实现原理:通过Condition的等待/通知实现生产者和消费者的通信。

Fork/Join框架

设计:

  1. 分割任务
  2. 执行任务并合并结果

实现:

  1. 创建ForkJoinTask,继承ForkJoinTask的子类
    1. RecursiveAction:无返回结果
    2. RecursiveTask:有返回结果
  2. 调用ForkJoinPool执行ForkJoinTask:forkJoinPool.submit(task);

原子操作类

  1. 原子更新基本类型类:AtomicBoolean AtomicInteger AtomicLong
  2. 原子更新数组:AtomicBooleanArray AtomicIntegerArray AtomicLongArray AtomicReferenceArray
  3. 原子更新引用类型:AtomicReference AtomicReferenceFieldUpdater AtomicMarkableReference
  4. 原子更新字段类:AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicStampedReference

原理
使用CAS操作:Unsafe类提供了3种CAS方法:compareAndSwapObject compareAndSwapInt compareAndSwapLong

并发工具类

CountDownLatch 递减的计数器

countDown() getCount() await()
等待N个线程执行完成,功能类似Join,但是提供了await方法,可以用于指定时间后不再阻塞当前线程。count是同步队列中的数量。

CyclicBarrier 同步屏障

让一组线程到达一个屏障时被阻塞,知道最后一个线程到达屏障时,所有线程才会继续运行。
可用于多线程计算数据后合并计算结果。

CountDownLatch和CyclicBarrier区别

countDownLatch是一次性的,CyclicBarrier可以reset

CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。

总结:
CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的。

而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流。

Semaphore 信号量

acquire()获取许可证 release()释放许可证 tryAcquire()尝试获取许可证
控制只有指定数量的线程获取到资源。

Exchanger 交换者

exchange(T t) 交换数据
交换两个线程的数据。一个线程先执行exchange()等待另一个线程,另一个线程执行exchange()后,交换两个线程的数据。

线程池

处理流程

execute流程.png

新任务提交到线程池 -->

  1. 核心线程池 是否满了?
    1. 未满,创建线程执行任务
    2. 已满,下个流程
  2. 队列 是否满了?
    1. 未满,将任务存储在队列中
    2. 已满,下个流程
  3. 线程池 是否满了?
    1. 未满,创建线程执行任务
    2. 已满,按照策略处理无法执行的任务

线程池创建线程时,会将线程封装成工作线程Worker,Worker执行完任务后,还会循环从工作队列中获取任务来执行。

合理配置线程池

根据以下几个角度分析:

  1. 任务的性质:CPU密集型任务、IO密集型任务和混合型任务
  2. 任务的优先级:高、中、低
  3. 任务的执行时间:长、中、短
  4. 任务的依赖性:是否依赖其它系统资源,如数据库连接

性质
CPU密集型任务应配置尽可能小的线程,如:N(cpu)+1
IO密集型任务应配置尽可能多的线程,如:2*N(cpu)
混合型任务首先考虑拆分为前两个,如果拆分后两个任务的执行时间相差太大,则没有拆分的必要。

优先级
一般考虑使用优先级队列,注意低优先级任务可能会没机会执行

执行时间
给执行时间不同的任务分配不同的线程池,或者使用优先级队列让时间短的任务先执行

依赖性
等待时间越长,线程数越多,反之越少

建议使用有界队列

监控线程池

largestPoolSize 线程池中曾经创建过的最大线程数量
getActiveCount 获取活动的线程数

通过继承线程池自定义线程池,重写任务执行前后和线程池关闭前执行的方法进行监控。例如:监控任务的最大、最小和平均执行时间

Executor框架

任务的两级调度模型

任务的两级调度模型.png

Executor框架使用示意图

Executor框架使用示意图.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容