猫头鹰的深夜翻译:核心JAVA并发一
简介
从创建以来,JAVA就支持核心的并发概念如线程和锁。这篇文章会帮助从事多线程编程的JAVA开发人员理解核心的并发概念以及如何使用它们。
(博主将在其中加上自己的理解以及自己想出的例子作为补充)
概念
原子性:原子操作是指该系列操作要么全部执行,要么全部不执行,因此不存在部分执行的状态。
可见性:一个线程能够看见另一个线程所带来的改变。
竞争情况
当多个线程在一个共享的资源上执行一组操作时,会产生竞争。根据各个线程执行操作的顺序可能产生多个不同结果。下面的代码不是线程安全的,value
可能会被初始化多次,因为check-then-act
型(先判断是否为null
,然后初始化)的惰性初始化并非原子性操作。
1 |
class <T> { |
数据冲突
当两个或多个线程在没有同步的情况下试图访问同一个非final
变量时,会产生数据冲突。不使用同步可能使数据的改变对别的线程不可见,从而可能读取过期的数据,并导致如无限循环,数据结构损坏和不准确的计算等后果。下面这段代码可能会导致无限循环,因为读者线程可能永远都没有看到写入者线程做出的更改:
1 |
class Waiter implements Runnable { |
JAVA内存模型:happens-before关系
JAVA内存模型是根据读写字段等操作来定义的,并在控制器上进行同步。操作根据happens-before关联
排序,这解释了一个线程何时能够看到另一个线程操作的结果,以及是什么构成了一个同步良好的程序。
happens-before关联
有以下属性:
Thread#start
的方法在线程的所有操作之前执行- 在释放当前控制器之后,后序的请求才可以获取控制器。(Releasing a monitor happens before any subsequent acquisition of the same monitor.)
- 写入
volatile
变量的操作在所有后序读取该变量的操作之前执行。 - 写入
final
型变量的操作在发布该对象的引用之前执行 - 线程的所有操作在从
Thread#join
方法返回之前执行
上图中,Action X
在Action Y
之前执行,因此线程1
在Action X
以前执行的所有操作对线程2
在Action Y
之后的所有操作可见。
标注的同步功能
synchronized关键字
synchronized
关键字用来防止不同的线程同时进入一段代码。它确保了你的操作的原子性,因为你只有获得了这段代码的锁才能进入这段代码,使得该锁所保护的数据可以在独占模式下操作。除此以外,它还确保了别的线程在获得了同样的锁之后,能够观察到之前线程的操作。
1 |
class AtomicOperation { |
synchronized
关键字也可以在方法层上声明。
静态方法:将持有该方法的类作为加锁对象
非静态方法:加锁this
指针
锁是可重入的。所以如果一个线程已经持有了该锁,它可以一直访问该锁下的任何内容:
1 |
class Reentrantcy { |
争用程度影响如何获得控制器:
初始化:刚刚创建,没有被获取
biased:锁下的代码只被一个线程执行,不会产生冲突
thin:控制器被几个线程无冲突的获取。使用CAS(compare and swap)
来管理这个锁
fat:产生冲突。JVM请求操作系统互斥,并让操作系统调度程序处理线程停放和唤醒。
wait/notify
wait/notify/notifyAll
方法在Object
类中声明。wait
方法用来将线程状态改变为WAITING
或是TIMED_WAITING
(如果传入了超时时间值)。要想唤醒一个线程,下列的操作都可以实现:
- 另一个线程调用
notify
方法,唤醒在控制器上等待的任意的一个线程 - 另一个线程调用
notifyAll
方法,唤醒在该控制器上等待的所有线程 Thread#interrupt
方法被调用,在这种情况下,会抛出InterruptedException
最常用的一个模式是一个条件性循环:
1 |
class ConditionLoop { |
- 记住,要想使用对象上的
wait/notify/notifyAll
方法,你首先需要获取对象的锁 - 总是在一个条件性循环中等待,从而解决如果另一个线程在wait开始之前满足条件并且调用了
notifyAll
而导致的顺序问题。而且它还防止线程由于伪唤起继续执行。 - 时刻确保你在调用
notify/notifyAll
之前已经满足了等待条件。如果不这样的话,将只会发出一个唤醒通知,但是在该等待条件上的线程永远无法跳出其等待循环。
博主备注:这里解释一下为何建议将wait
放在条件性循环中、假设现在有一个线程,并没有将wait放入条件性循环中,代码如下:大专栏 猫头鹰的深夜翻译:核心JAVA并发一r/>
1 |
class UnconditionLoop{ |
假设现在有两个线程分别同时调用waitForCondition
和satisfyCondition()
,而调用satisfyCondition的方法先调用完成,并且发出了notifyAll
通知。鉴于waitForCondition
方法根本没有进入wait
方法,因此它就错过了这个解挂信号,从而永远无法被唤醒。
这时你可能会想,那就使用if
判断一下条件呗,如果条件还没满足,就进入挂起状态,一旦接收到信号,就可以直接执行后序程序。代码如下:
1 |
class UnconditionLoop{ |
那让我们再假设这个 方法中还存在另一个condition,并且也有其对应的等待和唤醒方法。假设这时satisfyConsition2
被满足并发出nofityAll
唤醒所有等待的线程,那么waitForCondition
和waitForCondition2
都将会被唤醒继续执行。而waitForCondition
的条件并没有被满足!
因此在条件中循环等待信号是有必要的。
volatile关键字
volatile
关键字解决了可见性问题,并且使值的更改原子化,因为这里存在一个happens-before
关联:对volatile
值的更改会在所有后续读取该值的操作之前执行。因此,它确保了后序所有的读取操作能够看到之前的更改。
1 |
class VolatileFlag implements Runnable { |
Atomics
java.util.concurrent.atomic
包中包含了一组支持在单一值上进行多种原子性操作的类,从而从加锁中解脱出来。
使用AtomicXXX
类,可以实现原子性的check-then-act
操作:
1 |
class CheckThenAct { |
AtomicInteger
和AtomicLong
都用increment/decrement
操作:
1 |
class Increment { |
如果你想要创建一个计数器,但是并不需要原子性的读操作,可以使用
LongAdder
替代AtomicLong/AtomicInteger
,LongAdder
在多个单元格中维护该值,并在需要时对这些值同时递增,从而在高并发的情况下性能更好。
ThreadLocal
在线程中包含数据并且不需要锁定的一种方法是使用ThreadLocal存储。从概念上将,ThreadLocal就好像是在每个线程中都有自己版本的变量。ThreadLocal常用来存储只属于线程自己的值,比如当前的事务以及其它资源。而且,它还能用来维护单个线程专有的计数器,统计或是ID生成器。
1 |
class TransactionManager { |
原文链接: https://dzone.com/refcardz/core-java-concurrency?chapter=1
猫头鹰的深夜翻译:核心JAVA并发一的更多相关文章
- 【Java并发核心三】CountDownLatch、CyclicBarrier及Phaser
个人感觉,看书学习还是需要“不求甚解”,因为一旦太过于计较小的得失,就容易钻牛角尖,学习进度也慢.我们完全可以先学一个大概,等到真正用到的时候再把那些细节丰富起来,就更有针对性. 所以,针对java并 ...
- 【java并发核心一】Semaphore 的使用思路
最近在看一本书<Java并发编程 核心方法与框架>,打算一边学习一边把学习的经验记下来,所粘贴的代码都是我运行过的,大家一起学习,欢迎吐槽. 估计也没多少人看我的博客,哈哈,那么我还是会记 ...
- 【转】Java 并发编程:核心理论
并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系 ...
- Java并发编程核心知识体系精讲
第1章 开宗明义[不看错过一个亿]本章一连串设问:为什么学并发编程?学并发编程痛点?谁适合学习本课?本课程包含内容和亮点?首先4大个理由告诉你为什么要学,其实源于JD岗位要求就不得不服了.其次5个痛点 ...
- (转)Java并发编程:核心理论
原文链接:https://www.cnblogs.com/paddix/p/5374810.html Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及 ...
- Java 并发编程:核心理论(一)
前言......... 并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可 ...
- Java并发编程-核心问题(1)
一.常见问题 从小的方面讲, 并发编程最常见的问题就是可见性.原子性和有序性问题. 从大的方面讲, 并发编程最常见的问题就是安全性问题.活跃性问题和性能问题. 下面主要从微观上分析问题. 二.可见性问 ...
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- [转]Java并发的四种风味:Thread、Executor、ForkJoin和Actor
这篇文章讨论了Java应用中并行处理的多种方法.从自己管理Java线程,到各种更好几的解决方法,Executor服务.ForkJoin 框架以及计算中的Actor模型. Java并发编程的4种风格:T ...
随机推荐
- ZJNU 2201 - 挖矿谷物语
在dfs过程中加上栈记录当次dfs走过的路径 如果当次dfs到了一个之前的dfs已经经过的点 又因为只对没有访问过的点开始dfs 所以这种情况就说明接下来不可能返回到当次dfs开始的点 将栈内元素取出 ...
- Redis分布式锁前世今生
1.redis锁前世即基于单Redis节点的分布式锁,诸如setkey value px milliseconds nx 前世者,必将经历种种磨砺,才能稍微符合一些主流.推荐自测非常好用的redis工 ...
- android新闻项目、饮食助手、下拉刷新、自定义View进度条、ReactNative阅读器等源码
Android精选源码 Android仿照36Kr官方新闻项目课程源码 一个优雅美观的下拉刷新布局,众多样式可选 安卓版本的VegaScroll滚动布局 android物流详情的弹框 健身饮食记录助手 ...
- lr cc安装后提示“内部出现错误,无法创建文件夹”的解决办法
好多人在使用lr过程中提示“内部出现错误,无法创建文件夹”,今天MacW小编给大家带来了解决的方法,一起来看看吧! 1.此问题主要是用户权限的问题. 下载这个shell 脚本,此 shell 脚本可自 ...
- 关于tomcat报错记录
启动报错关键信息如下: Caused by: java.lang.IllegalStateException: Unable to complete the scan for annotations ...
- 黑马eesy_15 Vue:常用语法
自学Java后端开发,发现14 微服务电商[乐优商城]实战项目,在介绍完SpringCloud后就要肝前端的基础知识ES6语法和Vue.js 所以本篇博客作为入门Vue练习记录的过程,目的是供自学后端 ...
- More 'long-life' plastic bags being used
1 1.1 roll out v. 推广,或实行 1.2 pilot v. 试行 n. 飞行员 1.3 bags for life 可重复使用的环保购物袋 2 2.1 How many times a ...
- 2019-2020-1 20199324《Linux内核原理与分析》第八周作业
第七章 可执行程序工作原理 一.ELF目标文件格式 目标文件:ABI,应用程序二进制接口,是编译器生成的文件. ELF:可执行的和可链接的格式,是一个目标文件格式的标准.三种类型是: 可重定位文件:L ...
- Linux基础篇一:Linux历史
记得有个GNU组织,里面有个GPL协议(通用版权许可协议),如果软件被打上GPL,那么任何人都可以对这个软件进行修改.
- Docker系列三:Dockerfile
Dockerfile是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像 Dockerfile由一行行命令语句组成,支持#开头的注释 Dockerfile分为四部分:基础镜像信 ...