- 浏览: 2573427 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (880)
- 每日总结 (26)
- java (37)
- lucene 2.4源码学习 (11)
- 庖丁分词的源码分析 (5)
- cindy源码阅读 (10)
- jetty (3)
- java基础 (49)
- hadoop (37)
- python (7)
- 那些蛋疼的事 (15)
- 测试 (5)
- spring (3)
- redis (4)
- git (4)
- 我的开源项目 (2)
- linux (15)
- mysql (8)
- bootsharp (1)
- idea (1)
- Disruptor源码分析 (11)
- 高并发 (22)
- jdk (4)
- 领域驱动 (1)
- 坑 (6)
- 表达式框架 (7)
- 游戏 (3)
- Guava (1)
- 缓存 (1)
- 数据库 (1)
- 游戏项目 (3)
- java8 (1)
最新评论
-
hsluoyz:
PyCasbin是一个用Python语言打造的轻量级开源访问控 ...
权限管理的一个简单后台 -
liuyachao111:
谢谢 问题解决了
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾 -
jnjeC:
多谢博主分享,在配置文件里的&也要改成& ...
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾 -
大维啊:
同志,你这不行啊!
java 的 AccessController.doPrivileged使用 -
lisha2009:
解决了我的问题,多谢博主分享!
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾
可以参照http://blog.csdn.net/chen77716/article/details/6641477.本文的很多思想都来源于这个文章.这文章说的真的是很透.
我们主要关注lock和unlock方法
直接调用的是sync的lock.
默认是非公平锁,看看NonfairSync的lock方法:
compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从0改为1表示取得锁.这个时候设置获取锁的线程就是当前线程.
具体调用的是
unsafe的compareAndSwapInt方法是native的.
但是我们更关注的是,申请锁不成功的时候是怎么做的.可以看到是acquire(1);
acquire首先调用的是tryAcquire.看看NonfairSync的tryAcquire是怎么样实现的:
tryAcquire的逻辑是这样的,
c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.
current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加一
如果tryAcquire没有成功, 就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
addWaiter是把线程和线程的状态信息封装到一个node对象,node是个链表.
其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的.
compareAndSetTail 这个方法就是尝试把当前线程放到一个链表的末尾去.
如果没有成功,执行enq(node);
for (;;) 这里用到了无限循环.
在addWaiter里,放进链表的条件是链表的结尾元素不能为null,在enq方法发现这种情况会创建一个node对象取代之前的链表的结尾元素.
现在来看acquireQueued方法:
acquireQueued也是个无限循环.就是说要么获取到锁,要么中断当前线程.
acquireQueued会再次调用tryAcquire,就是再尝试一次获取锁.
shouldParkAfterFailedAcquire是判断是否要中断当前线程.
shouldParkAfterFailedAcquire返回true的话,当前线程就直接中断了
返回false的话,会无限循环再来一次,期间会删除掉废弃的node(pred.waitStatus > 0)
会一直尝试把前面的节点的waitStatus设置为SIGNAL,这个其实就是返回true的条件.
也就是说会不断尝试直到返回true,然后中断当前线程.
线程这个时候还没获取锁,但是已经被中断了,这个时候只有等待被唤醒,然后再尝试去或得锁.
这个逻辑是可以在unlock中看到的:
从tryRelease可以看到释放锁的条件是:c == 0 就是锁的计数为0;
unparkSuccessor:释放锁.
LockSupport.unpark(s.thread); 可以看到释放锁的时候会唤醒一个之前链表的一个线程,这样线程的加锁和解锁就串起来了. 这里最终调用的是unsafe.unpark.
线程等待最终调用的是unsafe.park.
线程唤醒最终调用的是unsafe.park.
可以看到unsafe才是最终的实现,也可以看到unsafe的方法都是native的.
我们主要关注lock和unlock方法
public void lock() { sync.lock(); }
直接调用的是sync的lock.
public ReentrantLock() { sync = new NonfairSync(); }
默认是非公平锁,看看NonfairSync的lock方法:
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从0改为1表示取得锁.这个时候设置获取锁的线程就是当前线程.
具体调用的是
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
unsafe的compareAndSwapInt方法是native的.
但是我们更关注的是,申请锁不成功的时候是怎么做的.可以看到是acquire(1);
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
acquire首先调用的是tryAcquire.看看NonfairSync的tryAcquire是怎么样实现的:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); 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"); setState(nextc); return true; } return false; }
tryAcquire的逻辑是这样的,
c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.
current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加一
如果tryAcquire没有成功, 就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
addWaiter是把线程和线程的状态信息封装到一个node对象,node是个链表.
其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的.
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; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
compareAndSetTail 这个方法就是尝试把当前线程放到一个链表的末尾去.
如果没有成功,执行enq(node);
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize Node h = new Node(); // Dummy header h.next = node; node.prev = h; if (compareAndSetHead(h)) { tail = node; return h; } } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
for (;;) 这里用到了无限循环.
在addWaiter里,放进链表的条件是链表的结尾元素不能为null,在enq方法发现这种情况会创建一个node对象取代之前的链表的结尾元素.
现在来看acquireQueued方法:
final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } }
acquireQueued也是个无限循环.就是说要么获取到锁,要么中断当前线程.
acquireQueued会再次调用tryAcquire,就是再尝试一次获取锁.
shouldParkAfterFailedAcquire是判断是否要中断当前线程.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
shouldParkAfterFailedAcquire返回true的话,当前线程就直接中断了
返回false的话,会无限循环再来一次,期间会删除掉废弃的node(pred.waitStatus > 0)
会一直尝试把前面的节点的waitStatus设置为SIGNAL,这个其实就是返回true的条件.
也就是说会不断尝试直到返回true,然后中断当前线程.
线程这个时候还没获取锁,但是已经被中断了,这个时候只有等待被唤醒,然后再尝试去或得锁.
这个逻辑是可以在unlock中看到的:
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
从tryRelease可以看到释放锁的条件是:c == 0 就是锁的计数为0;
unparkSuccessor:释放锁.
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
LockSupport.unpark(s.thread); 可以看到释放锁的时候会唤醒一个之前链表的一个线程,这样线程的加锁和解锁就串起来了. 这里最终调用的是unsafe.unpark.
线程等待最终调用的是unsafe.park.
线程唤醒最终调用的是unsafe.park.
可以看到unsafe才是最终的实现,也可以看到unsafe的方法都是native的.
发表评论
-
Quartz 1.8.0版本的死锁问题
2017-01-05 15:04 5670先说问题的结论:https:/ ... -
Calendar.getInstance()的坑
2016-12-06 16:50 5831Calendar.getInstance()看起来应该是个单例 ... -
java线程池自己掉进去的坑
2016-08-18 17:59 2084java线程池的maximumPoolSize的生效条件真的是 ... -
java比AtomicLong 更高效的LongAdder
2016-04-14 21:05 3186AtomicLong 是通过cas来实现的,已经很高效了,还有 ... -
java避免缓存伪共享
2016-04-14 20:15 907java8之前的做法: 加6个无用的long字段 如: ... -
java重复批次执行
2015-04-21 20:39 1465方案1 使用Phaser 方案2 使用CyclicBarr ... -
两个线程,分别打印[1,3,5]和[2,4,6],写一个程序,打印[1,2,3,4,5,6]。
2015-04-21 17:13 3905两个线程,分别打印[1,3,5]和[2,4,6],写一个程序, ... -
mina实现相同session后到的请求一定后处理的原理
2015-03-26 22:04 3708mina后面的业务处理是一个线程池去处理的,在线程池的多线程的 ... -
简单实现一个java公平策略的锁
2015-03-24 21:40 1370代码是: package com.eyu.gift.loc ... -
对java的BitSet的多线程并发的探索
2015-03-23 14:45 4136java的BitSet不是线程安全的,所以多线程的时候h要加锁 ... -
java的Condition 加强版的wait notify
2015-01-29 20:26 5209Lock对应Synchronized,使用之前都要先获取锁 ... -
java用cyclicBarrier来实现Phaser的分段功能
2015-01-26 10:22 1555cyclicBarrier是有自动重置功能的,我们可以用这个功 ... -
java的Phaser,多线程分阶段执行
2015-01-25 12:00 1915Phaser是处理分阶段的多线程的很好的方法: 比如需求是: ... -
spring启动的时候尝试多线程发生的死锁
2014-09-12 11:12 3888具体的死锁线程dump: Found one Java-l ... -
System.nanoTime()的使用
2013-09-19 17:47 44517纳秒 ns(nanosecond):纳秒, 时间单位。一秒 ... -
java原子更新器AtomicReferenceFieldUpdater的使用
2013-09-19 11:32 15249AtomicReferenceFieldUpdater ... -
linux下文件的append是原子的(线程安全的)
2013-09-17 15:07 2819具体的理由可以参考这个文章:http://www.pagefa ... -
java Unsafe类的compareAndSwap方法
2013-09-15 12:43 18516compareAndSwap是个原子方法,原理是cas.就是说 ... -
java 反射的field.setAccessible(true)
2013-09-15 11:43 15301在java的反射使用中,如果字段是私有的,那么必须要对这个字段 ... -
转 Java的Volatile
2013-09-07 17:24 1309原文:http://ifeve.com/syn-jmm-vol ...
相关推荐
近日,阅读jdk并发包源码分析整理笔记。
ReentrantLock 实现原理 1
主要为大家详细介绍了Java并发系列之ReentrantLock源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
什么是公平锁和非公平锁 公平与非公平的一个很本质的区别就是,是否遵守FIFO(也就是先来后到)。当有多个线程来申请锁的时候,是否先申请的线程先获取锁,后申请的线程后获取锁?如果是的,则是 公平锁 ,否则是...
主要介绍了ReentrantLock源码之条件锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面我们来一起学习一下吧
ReentrantLock源码剖析
7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf
本文将对ReentrantLock实现原理进行详细的介绍,具有很好的参考价值,下面跟着小编一起来看下吧
第六章 ReentrantLock源码解析2--释放锁unlock()最常用的方式://注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁
前言 鼠年新的工作日开始了,新的一年新的工作学习,第一个工作日给自己定一些要求吧。 想写一个系列【隐匿撕源码...ReentrantLock源码 我们来看ReentrantLock的构造方法 有两种 传入boolean值 选择此锁是否是公平锁 默
第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()最常用的方式://注意:通常情况下,这个会设置成一个类变量,比如说Segemen
ReentrantLock java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。这篇文章主要是从...
ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。下面我们来深入了解一下它吧
java语言 并发编程 ReentrantLock与synchronized区别 详解
主要为大家详细介绍了Java并发系列之ReentrantLock源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
ReentrantLock的使用及注意事项
1.1 AQS源码解析 https://blog.csdn.net/qq_34125999/article/details/105343472 1.2 Sync /** * ReentrantLock 基础结构 */ abstract static class Sync extends AbstractQueuedSynchronizer { private static ...
这份资源旨在详细讲解 Java 中的 Locks 框架,特别关注 ReentrantLock 的使用和原理。Locks 框架提供了比传统的 synchronized 关键字更强大、更灵活的线程同步机制,而 ReentrantLock 是其中的一种重要实现。 Locks ...
8. Lock接口 (ReentrantLock 可重入锁) 特性 ReentantLock 继承接口 Lock 并实现了接口中定义的方法, 它是一种可重入锁, 除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁...
原理 synchronized关键字是通过字节码指令来实现的 synchronized关键字编译后会在同步块前后形成monitorenter和monitorexit两个字节码指令 执行monitorenter指令时需要先获得对象的锁(每个对象有一个监视器锁...