- 浏览: 2609488 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (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 5935先说问题的结论:https:/ ... -
Calendar.getInstance()的坑
2016-12-06 16:50 5994Calendar.getInstance()看起来应该是个单例 ... -
java线程池自己掉进去的坑
2016-08-18 17:59 2139java线程池的maximumPoolSize的生效条件真的是 ... -
java比AtomicLong 更高效的LongAdder
2016-04-14 21:05 3229AtomicLong 是通过cas来实现的,已经很高效了,还有 ... -
java避免缓存伪共享
2016-04-14 20:15 953java8之前的做法: 加6个无用的long字段 如: ... -
java重复批次执行
2015-04-21 20:39 1500方案1 使用Phaser 方案2 使用CyclicBarr ... -
两个线程,分别打印[1,3,5]和[2,4,6],写一个程序,打印[1,2,3,4,5,6]。
2015-04-21 17:13 3965两个线程,分别打印[1,3,5]和[2,4,6],写一个程序, ... -
mina实现相同session后到的请求一定后处理的原理
2015-03-26 22:04 3752mina后面的业务处理是一个线程池去处理的,在线程池的多线程的 ... -
简单实现一个java公平策略的锁
2015-03-24 21:40 1415代码是: package com.eyu.gift.loc ... -
对java的BitSet的多线程并发的探索
2015-03-23 14:45 4227java的BitSet不是线程安全的,所以多线程的时候h要加锁 ... -
java的Condition 加强版的wait notify
2015-01-29 20:26 5274Lock对应Synchronized,使用之前都要先获取锁 ... -
java用cyclicBarrier来实现Phaser的分段功能
2015-01-26 10:22 1598cyclicBarrier是有自动重置功能的,我们可以用这个功 ... -
java的Phaser,多线程分阶段执行
2015-01-25 12:00 1967Phaser是处理分阶段的多线程的很好的方法: 比如需求是: ... -
spring启动的时候尝试多线程发生的死锁
2014-09-12 11:12 3953具体的死锁线程dump: Found one Java-l ... -
System.nanoTime()的使用
2013-09-19 17:47 44583纳秒 ns(nanosecond):纳秒, 时间单位。一秒 ... -
java原子更新器AtomicReferenceFieldUpdater的使用
2013-09-19 11:32 15361AtomicReferenceFieldUpdater ... -
linux下文件的append是原子的(线程安全的)
2013-09-17 15:07 2888具体的理由可以参考这个文章:http://www.pagefa ... -
java Unsafe类的compareAndSwap方法
2013-09-15 12:43 18619compareAndSwap是个原子方法,原理是cas.就是说 ... -
java 反射的field.setAccessible(true)
2013-09-15 11:43 15410在java的反射使用中,如果字段是私有的,那么必须要对这个字段 ... -
转 Java的Volatile
2013-09-07 17:24 1364原文:http://ifeve.com/syn-jmm-vol ...
相关推荐
8. **多线程同步**:synchronized关键字、Lock接口及其实现如ReentrantLock,以及并发工具类如Atomic系列,源码解读能揭示其内部同步机制,提升多线程编程能力。 9. **字符串处理**:String类的不可变性、...
Java AQS(AbstractQueuedSynchronizer)是一个强大的同步组件,它被广泛应用于并发编程中,如Java的Lock...通过对AQS源码的深入解读,我们可以更好地理解并发编程的底层机制,从而编写出更加高效和健壮的多线程代码。
根据提供的文件信息,本文将对“JAVA源码JAVA网络通信系统的研究与开发(论文+源代码+开题报告)”中的关键技术点进行详细解读,并结合相关内容分析该系统的实现原理及应用场景。 ### 一、项目背景 随着互联网技术的...
10. **Java并发编程**:包括创建线程的三种方式、线程同步机制Synchronized和ReentrantLock、线程池、死锁、volatile关键字和CAS原子操作。 11. **Java并发集合**:ArrayBlockingQueue、LinkedBlockingQueue、...
由浅入深,通过图解和手写代码,讲解Java版...AQS原理&源码解读 线程同步+各种锁的原理&手写实现 JDK多线程工具包中,若干种工具的原理和手写实现: ReentrantLock、CountDownLanuh、CyclicBarrier、Semaphore
综上所述,这个“Java学习”主题涵盖了线程这一核心概念,涉及线程的创建、管理和同步,可能还包括了对Java源码的解读以及开发工具的使用。通过深入学习这部分内容,开发者能够提升自己在并发编程领域的技能,更好地...
标签“源码”提示我们分析可能会涉及JDK的源代码阅读,通过查看原始实现来理解其工作原理,这对于解决复杂问题或优化性能是至关重要的。而“工具”可能意味着会介绍一些辅助分析的工具,如代码分析工具、性能监控...
从提供的文件内容中,我们可以得知这本《java面试宝典》旨在帮助求职者全面准备Java开发相关的技术面试,其中不仅包含了大量关于Java核心知识点的解析,还涉及了并发编程、JVM原理以及源码层面的深度理解,非常适合...
以下是对这些知识点的详细解读: 1. **基础语法**:面试中常常会考察对Java基础语法的掌握,如数据类型、变量、运算符、流程控制语句(if、switch、for、while)、方法、类与对象等。此外,封装、继承和多态作为...
以下是对该文档中提到的一些核心概念和知识点的深入解读。 ### 第一部分:基本语法 #### 1. 关键字 - **static**:用于定义静态变量、方法或内部类。静态成员属于类本身,而不是类的实例。 - **final**:用于声明...