您的位置:首页 > 编程语言 > Java开发

ReentrantLock源码分析(一)

2016-03-27 00:00 911 查看
摘要: ReentrantLock的源码和原理分析,本文主要是源码分析前的一些准备知识。

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

unparkinterrupt的区别只是在于: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息