ReentrantLock源码分析(一)
2016-03-27 00:00
911 查看
摘要: ReentrantLock的源码和原理分析,本文主要是源码分析前的一些准备知识。
ReentrantLock源码分析(一)
本文为博主原创文章,转载请注明出处:http://my.oschina.net/dongxu87/blog
下面一段代码可以告诉大家为什么它叫可重入锁。
可以看到,该程序会连续调用两次lock,然后又连续调用两次unlock。可以自己试一下,第二次lock的时候并不会因为已经lock过而锁住,并且第一次unlock的时候锁并没有释放锁。
非可重入的锁 如果连续对相同的锁lock两次,线程会自己和自己产生死锁,这是非常危险的事情。
个人认为:非可重入的锁没有实际的使用价值,想参观的童鞋可以看一下jdk中AbstractQueuedSynchronizer类最上边的注释中,有一个简单实现的锁示例,它就是一个非可重入锁。
可重入锁 的意思其实就是 对于同一个线程,连续多次调用lock并不会产生死锁,并且连续调用相同次数的unlock后,才能释放锁
现在我们再看看JVM源码中这个CAS到底做了些啥?这里以compareAndSwapInt为例:
好吧,内嵌汇编,博主已被亮瞎。但是看汇编代码的第一句LOCK_IF_MP,还是挺好理解的:如果是多处理器,那么就锁定。既然是直接调用的汇编代码,那么unsafe.compareAnsSwapXXX的效率应该不低。其他的就不多说了,博主对汇编一窍不通。
我们只需要了解:当一个线程调用park函数时,会让自身进入休眠状态,此时如果用jstack查看线程状态,可以看到被park的线程是这样的:
状态是WAITING(parking)。当其他线程调用unpark(thisThread)后或者thisThread.interrupt(),这个线程就会恢复执行,恢复后的线程是这样:
状态是RUNNABLE
unpark和interrupt的区别只是在于:interrupt会在线程恢复执行后,为线程置一个中断标识位,可以在代码中使用isInterrupted或interruped方法来判断,有些线程操作会抛出InterruptedException,比如Thread.sleep,有些则会忽略interrupt,让线程继续阻塞,比如ReentrantLock.lock。具体的Interrupt标志位处理方式与具体使用场景相关。
关于park/unpark操作,博主参考了一位大神的文章:Java的LockSupport.park实现分析。实际上被park的线程不再被操作系统调度,而是休眠等待一个信号来唤醒它。这一点跟自旋锁是完全不一样的。
【第一次用Markdown来写博客,感觉还不错,但生成的html格式和目标格式和markdown编辑器中看到的差距甚远,只能再编辑器中二次编辑。】
generated by haroopad
ReentrantLock源码分析(一)
本文为博主原创文章,转载请注明出处:http://my.oschina.net/dongxu87/blog
前言
现在网上已有不少关于ReentrantLock和AQS的原理源码分析,但博主还是想自己写一份,一是按照自己的源码分析习惯来写,以后要用了会更容易看懂。二是培养自己的写技术博文的习惯。三、从小语文差,顺便培养一下写作能力,搞技术的不搞搞博客咋个行勒?1. 什么是ReentrantLock?
博主并不是想教大家怎么使用ReentrantLock,或者把标题换成“什么是Reentrant”会更合适一些。Reentrant即”可重入”,ReentrantLock即是”可重入锁”。下面一段代码可以告诉大家为什么它叫可重入锁。
private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { lock.lock(); try { doSomething(); } finally { lock.unlock(); } } private static void doSomething() { // lock again lock.lock(); try { // do something in lock } finally { lock.unlock(); } }
可以看到,该程序会连续调用两次lock,然后又连续调用两次unlock。可以自己试一下,第二次lock的时候并不会因为已经lock过而锁住,并且第一次unlock的时候锁并没有释放锁。
非可重入的锁 如果连续对相同的锁lock两次,线程会自己和自己产生死锁,这是非常危险的事情。
个人认为:非可重入的锁没有实际的使用价值,想参观的童鞋可以看一下jdk中AbstractQueuedSynchronizer类最上边的注释中,有一个简单实现的锁示例,它就是一个非可重入锁。
可重入锁 的意思其实就是 对于同一个线程,连续多次调用lock并不会产生死锁,并且连续调用相同次数的unlock后,才能释放锁
2. 学习ReentrantLock之前需要了解的一点知识
2.1 CAS操作
全称:compare and swap(或者set)操作。详细说明可以看下边的代码注释。所有CAS操作具有原子性、原子性、原子性(重要的东西说三遍)/** * 注:以下注释是博主加上去的 * * 对目标对象中偏移量为offset的字段进行CAS操作,如果该字段全等于(即==)expectValue, * 则替换为targetValue并返回true表示替换成功,否则不做任何替换操作并返回false * 表示替换失败,整个过程是原子性的。 * * @params targetObject: 需要进行字段替换的目标对象 * @params offset: 目标对象中的目标字段偏移量 * @params expectValue: 期望目标字段等于的值 * @params targetValue: 目标字段将被替换成的目标值 **/ public final native boolean compareAndSwapObject(Object targetObject, long offset, Object expectValue, Object targetValue); // 后边两个compareAndSwapXXX操作和上边一个差不多,区别在于compareAndSwapObject操作的是Object字段, // 而compareAndSwapInt和compareAndSwapLong操作的是int和long public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
现在我们再看看JVM源码中这个CAS到底做了些啥?这里以compareAndSwapInt为例:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj); jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x, addr, e)) == e; UNSAFE_END // windows 下的Atomic::cmpxchg, // jdk源码文件:hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } } // linux下的Atomic::cmpxchg // jdk源码文件:hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp = os::is_MP(); __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" : "=a" (exchange_value) : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) : "cc", "memory"); return exchange_value; } // hotspot/src/os_cpu/ 下可以看到Atomic::cmpxchg针对不同操作系统实际上有很多不同的实现,就不一一列举了。
好吧,内嵌汇编,博主已被亮瞎。但是看汇编代码的第一句LOCK_IF_MP,还是挺好理解的:如果是多处理器,那么就锁定。既然是直接调用的汇编代码,那么unsafe.compareAnsSwapXXX的效率应该不低。其他的就不多说了,博主对汇编一窍不通。
2.2 LockSupport下的park和unpark
public static void park() { UNSAFE.park(false, 0L); } public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
我们只需要了解:当一个线程调用park函数时,会让自身进入休眠状态,此时如果用jstack查看线程状态,可以看到被park的线程是这样的:
"I am the main thread" #1 prio=5 os_prio=0 tid=0x00007fa71000a800 nid=0x7ae2 waiting on condition [0x00007fa716cbc000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d801b5a0> (a java.lang.Object) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
状态是WAITING(parking)。当其他线程调用unpark(thisThread)后或者thisThread.interrupt(),这个线程就会恢复执行,恢复后的线程是这样:
"I am the main thread" #1 prio=5 os_prio=0 tid=0x00007f770400a800 nid=0x7dad runnable [0x00007f770a470000] java.lang.Thread.State: RUNNABLE
状态是RUNNABLE
unpark和interrupt的区别只是在于:interrupt会在线程恢复执行后,为线程置一个中断标识位,可以在代码中使用isInterrupted或interruped方法来判断,有些线程操作会抛出InterruptedException,比如Thread.sleep,有些则会忽略interrupt,让线程继续阻塞,比如ReentrantLock.lock。具体的Interrupt标志位处理方式与具体使用场景相关。
关于park/unpark操作,博主参考了一位大神的文章:Java的LockSupport.park实现分析。实际上被park的线程不再被操作系统调度,而是休眠等待一个信号来唤醒它。这一点跟自旋锁是完全不一样的。
小结
本文主要讲解的是ReentranLock的概念和几个必要的jdk底层支持:包括CAS、park/unpark。实际上java并发包下的几乎所有高级并发数据结构,都是基于这两个简单的操作——CAS和park/unpark来实现的。有兴趣的朋友可以自己去了解一下,或者关注博主的后续文档。【第一次用Markdown来写博客,感觉还不错,但生成的html格式和目标格式和markdown编辑器中看到的差距甚远,只能再编辑器中二次编辑。】
generated by haroopad
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树