java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
前言:如有不正确的地方,还望指正。
目录
- 认识cpu、核心与线程
- java多线程系列(一)之java多线程技能
- java多线程系列(二)之对象变量的并发访问
- java多线程系列(三)之等待通知机制
- java多线程系列(四)之ReentrantLock的使用
- java多线程系列(五)之synchronized ReentrantLock volatile Atomic 原理分析
- java多线程系列(六)之线程池原理及其使用
Synchronized
原理
- synchronized关键字是通过字节码指令来实现的
- synchronized关键字编译后会在同步块前后形成monitorenter和monitorexit两个字节码指令
- 执行monitorenter指令时需要先获得对象的锁(每个对象有一个监视器锁monitor),如果这个对象没被锁或者当前线程已经获得此锁(也就是重入锁),那么锁的计数器+1。如果获取失败,那么当前线程阻塞,直到锁被对另一个线程释放
- 执行monitorexit指令时,计数器减一,当为0的时候锁释放
class Test
{
public int i=1;
public void test()
{
synchronized (this)
{
i++;
}
}
}
- 反编译后结果

volatile
作用
- 保证变量对所有的线程的可见性,当一个线程修改了这个变量的值,其他线程可以立即知道这个新值(之所以有可见性的问题,是因为java的内存模型)
原理
- 所有变量都存在主内存,每条线程有自己的工作内存,工作内存保存了被该线程使用的变量的主内存副本拷贝
- 线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存的变量,也就是必须先通过工作内存
- 一个线程不能访问另一个线程的工作内存
- volatile保证了变量更新的时候能够立即同步到主内存,使用变量的时候能立即从主内存刷新到工作内存,这样就保证了变量的可见性
- 实际上是通过内存屏障来实现的。语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。

Atomic
作用
- 当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以像自旋锁一样,继续尝试,一直等到执行成功。
原理
- CAS操作(compare and swap 对比和设置),是通过一个cpu指令实现的,这个指令是一个原子指令,指令有3个操作数ABC,A为内存位置,B为预期值,C为新值,如果A符合旧预期值B,那么用V更新A的值,如果不符合就不更新,这个过程是原子操作
- 所以我们并没有通过代码来实现同步,而是通过硬件级别的cpu指令来实现的,并不像synchronized一样阻塞线程
//加一并返回值
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
//返回CAS操作成功与否
public final boolean compareAndSet(int expect, int update) {
//根据变量在内存中的偏移地址valueOffset获取原值,然后和预期值except进行比,如果符合,用update值进行更新,这个过程是原子操作
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- 如果此时有两个线程,线程A得到current值为1,线程B得到current值也为2,此时线程A执行CAS操作,成功将值改为2,而此时线程B执行CAS操作,发现此时内存中的值并不是读到current值1,所以返回false,此时线程B继续进行循环,最后成功加1
Lock
作用
- 显式加锁
原理
- 通过同步器AQS(AbstractQueuedSynchronized类)来实现的,AQS根本上是通过一个双向队列来实现的
- 线程构造成一个节点,一个线程先尝试获得锁,如果获取锁失败,就将该线程加到队列尾部
- 非公平锁的lock方法,调用的sync(NonfairSync和fairSync的父类)的lock方法
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// ReentrantLock的lock方法
public void lock() {
sync.lock();
}
- NonfairSync的lock方法,acquire的是Sync的父类AQS的acquire方法
final void lock() {
//如果当前同步状态为0(锁未被占有),CAS操作设置同步状态,设置成功的话当前线程获得锁(如果此时是公平锁,那么不会执行compareAndSetState方法,直接acuire排队)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
//否则调用AQS的acquire方法
else
acquire(1);
}
//CAS设置锁的状态
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- AQS的acquire方法
//尝试获得锁,如果获取失败,将节点加入到尾节点
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire方法,尝试获得锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取state变量值
int c = getState();
if (c == 0) { //如果没有线程占用锁
if (compareAndSetState(0, acquires)) {
//设置当前线程获得锁
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) { //当前线程已经占用该锁
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 更新state值为新的重入次数
setState(nextc);
return true;
}
//获取锁失败
return false;
}
- 如果获取锁失败,将节点加入尾节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//如果尾节点不为空
if (pred != null) {
node.prev = pred;
//此时可能同时有其他线程插入,再进行判断(通过CAS),如果没有,将节点设置为尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果节点为空或者节点不为空并且有其他线程插入(CAS返回false),执行enq方法
enq(node);
return node;
}
- 如果节点为空或者节点不为空并且有其他线程插入(CAS返回false),执行enq
//通过自旋进行设置
private Node More enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 进入队列的线程尝试获得锁
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; //是否成功获取锁
try {
boolean interrupted = false; //线程是否被中断过
for (;;) {
final Node p = node.predecessor(); //获取前驱节点
//如果前驱是head尝试获锁
if (p == head && tryAcquire(arg)) {
setHead(node); // 获取成功,将当前节点设置为head节点
p.next = null; // 原head节点出队
failed = false;
return interrupted; //返回是否被中断过
}
// 前节点不是头节点或者获取失败,判断是否可以挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 线程若被中断,设置interrupted为true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 线程是否可以挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱节点状态为signal(此节点线程结束后唤醒下一个节点线程)
return true;
//如果 前驱节点状态为CANCELLED(线程已经被取消)
if (ws > 0) {
// 删除cancelled状态的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将前驱节点的状态设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 挂起当前线程,返回线程中断状态并重置
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析的更多相关文章
- Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例
概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...
- Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例
概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...
- java多线程系列6 synchronized 加强版 ReentrantLock
ReentrantLock类是可重入.互斥.实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力.ReenreantLock类的常用方法有: Re ...
- java多线程系列五、并发容器
一.ConcurrentHashMap 1.为什么要使用ConcurrentHashMap 在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,HashMap在 ...
- java多线程系列之 synchronized
一.synchronized基本原理 java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁.线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁. ...
- Java多线程系列五——列表类
参考资料: http://xxgblog.com/2016/04/02/traverse-list-thread-safe/ 一些列表类及其特性 类 线程安全 Iterator 特性 说明 Vect ...
- 【Java多线程系列五】列表类
一些列表类及其特性 类 线程安全 Iterator 特性 说明 Vector 是 fail-fast 内部方法用synchronized修饰,因此执行效率较低 1. 线程安全的列表类并不意味着调用它 ...
- Java多线程系列3 synchronized 关键词
先来看一个线程安全的例子 ,两个线程对count进行累加,共累加10万次. public class AddTest { public static void main(String[] args) ...
- (Java多线程系列五)守护线程
守护线程 什么是守护线程 Java中有两种线程,一种是用户线程,一种是守护线程. 当进程不存在或主线程停止,守护线程也会自动停止. class DaemonThread extends Thread ...
随机推荐
- workerman例子无法工作
现象 workerman已经正常启动,但是按照官网写的例子或者下载的demo无法工作,例如页面打不开,socket连接失败等 解决方法 一般这种workerman启动没报错,但是无法打开页面或者无法连 ...
- OpenCV学习3-----利用鼠标、键盘回调函数实现标定人体关节点
最近做实验,需要一些人体关节点的ground truth,需要自己手动标定,于是尝试使用OpenCV的鼠标键盘回调函数实现. 期间遇到不少问题,记录一下. 首先就是鼠标回调函数注册, namedWin ...
- 【LeetCode】119. Pascal's Triangle II
题目: Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3,Return [ ...
- VBS自动按键大全,vbs基本和特殊按键
CreateObject("Wscript.Shell") SendKeys [String] 脚本实现自动按键盘的某个键 过程是:按下F5间隔50毫秒松开F5间隔3000毫秒按下 ...
- 预编译语句(Prepared Statements)介绍,以MySQL为例
背景 本文重点讲述MySQL中的预编译语句并从MySQL的Connector/J源码出发讲述其在Java语言中相关使用. 注意:文中的描述与结论基于MySQL 5.7.16以及Connect/J 5. ...
- Java IO学习笔记(四)打印流
1.只有输出流才有打印流:PrintWriter和PrintStream分别针对字符和字节,提供了重载的print,Println方法用于多种数据类型的输出.PrintWriter和PrintStre ...
- gulp 入门指南
gulp 是基于 node 实现 Web 前端自动化开发的工具,利用它能够极大的提高开发效率. 在 Web 前端开发工作中有很多"重复工作",比如压缩CSS/JS文件.而这些工作都 ...
- MyBatis源码解析【1】准备工作
终于迎来了这一天,我觉得现在的我在经历了长时间的学习和开发之后有了一定的经验,所以准备开始学习源码. 今天我将做好充足的准备,在接下来的一个月中,努力的爬过这座大山.(可能不用一个月,但是我觉得需要仔 ...
- 关于js中的表单事件
表单结构如下所示: <form > <input type="text" name="txt" id="txt" valu ...
- java 数据库编程 学习笔记 不断更新
最近开始学习java,感觉java的数据库编程需要发个随笔记录一下,话不多说 切入正题. 一.数据库访问技术的简介 应用程序 → 执行SQL语句 →数据库 → 检索数据结果 → 应用程序 ( ...