`
suhuanzheng7784877
  • 浏览: 691637 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Ff8d036b-05a9-33b5-828a-2633bb68b7e6
读金庸故事,品程序人生
浏览量:47234
社区版块
存档分类
最新评论

Java分布式应用学习笔记06浅谈并发加锁机制分析

阅读更多

1.  前言

之前总结的多线程的调度、并发调度、线程加锁安全等等并发包底层大都使用了线程锁机制。咱们通过锁的源码来看看JDK如何将这些资源进行加锁限制的,怎么就能做到线程集中等待后就唤醒主线程的。

2.  一段并发包源码

以下是java.util.concurrent.CyclicBarrier的底层代码片段。

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            ……………………省略
        } finally {
            lock.unlock();
        }
    }

 

在执行等待的时候,里面使用了ReentrantLock对其进行资源加锁,保证在代码块中使用变量、读写变量不会被别的线程打扰。

3.  轻量级锁ReentrantLock

基于以上程序我们就来看看ReentrantLock内部是如何工作的。ReentrantLock内部有个Sync类,继承自AbstractQueuedSynchronizer,基于Sync又有2个子类继承于它,而ReentrantLock就是依靠这2Sync子类为内核实现的。代码大家直接看JDK源程序即可。

ReentrantLocklock()方法,它的加锁方法实际上是使用的内部静态类Sync的方法,至于调用的是公平——FairSync还是不公平的——NonfairSync,这个要看构建ReentrantLock的时候的构造函数了。

NonfairSync的加锁方法实现流程是这样的:首先基于CAS——Compare and Swap原则,先将state0尝试变成1。如果设置成功了,证明了一个事实——此时此刻,没有其他的线程持有该锁。则当前线程设置为ExclusiveOwnerThread(独家拥有线程);那么如果状态变量state设置不成功呢,则又揭示了一个事实,当前线程锁已经被其他线程所持有,那么调用acquire方法,该方法首先先尝试再次获取状态state,如果为0了,那么继续尝试设置状态为1。若成功则与此时无其他线程持有锁操作雷同。如果state依然不为0,则判断当前线程是否为独家拥有线程变量——exclusiveOwnerThread,是的话将state的值+1.如果不是,则将当前线程放入等待队列中挂起,挂起方法

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(false, 0L);
        setBlocker(t, null);
    }

 

FairSynclock()方法和NonfairSync大同小异。只不过它没有获取state变量信息的过程,直接是调用acquire(1)方法请求线程锁。

ReentrantLockunlock()方法,解锁的方法比较简单,不区分公平与不公平,都是获取当前state值,之后减去释放锁的个数,减操作后结果如果为0,表示锁可以释放了。通知队列上的那些线程,唤醒队列头线程,进行run操作。

这些加锁、解锁操作很明显都离不开队列——AbstractQueuedSynchronizer的辅助操作,将线程组织成为线程队列形式。

4.  读写锁ReentrantReadWriteLock

了解了ReentrantLock在加锁原理上使用了一把锁,一把钥匙开一把锁嘛~而如果遇到大部分操作是读操作的、而写操作比较少的时候使用ReentrantLock未免有点“奢侈”。使用ReentrantReadWriteLock——读写双锁进行读取和写入,在读多写少的场景下可以提升不少性能。它的基本工作原理是这样的:当使用读锁进行lock的时候,就算是有其他线程也进行读操作,而不是写操作的时候,线程不会阻塞,可以并行执行,和没有加lock几乎是一样的。当调用写锁的lock方法时,无论其他线程是什么性质的(读、写),都阻塞,所以这个双重锁适用于读操作频率较多、写操作频率较少的操作。咱们看个使用例子

	// 读写锁
	static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

			// 获得读锁-加锁
			reentrantReadWriteLock.readLock().lock();
			String str = listString.get(sum);
			reentrantReadWriteLock.readLock().unlock();

			// 获得写锁-加锁
			reentrantReadWriteLock.writeLock().lock();
			String str = Thread.currentThread().getName() + "--write:" + sum;
			listString.add(str);
			reentrantReadWriteLock.writeLock().unlock();

 因为一些特殊原因,不能将源码完整的场景全部给出,只能写出简单的使用关键的片段。

读锁的加锁源代码片段是

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

 

写锁的加锁源码片段是

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 

相比较而言,就是上面说过的一旦写锁加锁时发现有其他线程进行了操作,则将当前线程放置于线程等待队列中——之后再唤醒。而读操作锁直接进行了共享线程,并发读取。

5.  总结

综合前面几篇线程调度、多线程并发计算等等,底层都是基于加锁、抽象线程等待队列AbstractQueuedSynchronizer及其2个具体子类、CAS算法的综合体现。使用多线程并发包后我们可以构建高可用和高计算能力的分布式系统。

注意:如果没有将解锁代码写到finally块中,是有问题的!!如果发生了任何的运行时异常,会向上抛,那么锁会永远不会解除,那么造成的后果大家一定知道了。

31
5
分享到:
评论
20 楼 maketuwen198501 2011-08-27  
不错不错。
19 楼 hufei023090 2011-08-22  
恩,学习了啊
18 楼 suhuanzheng7784877 2011-08-22  
tan4836128 写道
有收获,顶楼主

献丑献丑,见笑见笑,可惜不适合你暴力的口味哈
17 楼 tan4836128 2011-08-22  
有收获,顶楼主
16 楼 你若无情我便休 2011-08-22  
3q.....
15 楼 suhuanzheng7784877 2011-08-22  
引用
我错了,在公司这样说话习惯了

不过的确一直觉得用java做中央控制不靠谱,所以如果不是中央控制,多台机器的同步是依赖分布式缓存的

而且概念上来说,分布式是指多台机器,而java的并发包是工作于本虚拟机内存的,两者其实是没有关系的

不知我说的有没有一丁点道理呢??呵呵

是是是,交流,互相学习才是最大的目的。我一直再提起你的一些良言,很中肯,对于我也是好的提醒与鞭策。snake1987不必太过认真~~
14 楼 snake1987 2011-08-22  
thomas_mule0086 写道
snake1987 写道
被分布式诱惑进来的~
建议改下题目~~
实际分布式跟java的并发包没有一毛钱的关系~

你没在构建分布式系统中用到这些并发技术,不代表别人没用过,小盆友,谦虚点。


我错了,在公司这样说话习惯了

不过的确一直觉得用java做中央控制不靠谱,所以如果不是中央控制,多台机器的同步是依赖分布式缓存的

而且概念上来说,分布式是指多台机器,而java的并发包是工作于本虚拟机内存的,两者其实是没有关系的

不知我说的有没有一丁点道理呢??呵呵

13 楼 suhuanzheng7784877 2011-08-22  
jilen 写道
并发就并发,不一定非要套个分布式吧

是是是,标题有点不太合适,这个笔者承认。呵呵
12 楼 suhuanzheng7784877 2011-08-22  
引用
你没在构建分布式系统中用到这些并发技术,不代表别人没用过,小盆友,谦虚点。

朋友,别那么火药味十足吧。
11 楼 suhuanzheng7784877 2011-08-22  
snake1987 写道
被分布式诱惑进来的~
建议改下题目~~
实际分布式跟java的并发包没有一毛钱的关系~

有些场景是在集群系统中做节点资源调度,处理不同节点、不同客户端、不同终端的时候并发的情况确实有关系的。
10 楼 suhuanzheng7784877 2011-08-22  
引用
搜狐畅游招聘Java开发工程师啦~!~!

这个得去招聘版块吧~~~
9 楼 suhuanzheng7784877 2011-08-22  
你若无情我便休 写道
问一下lz,这个东西和用synchronized有什么区别吗?

ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞。
synchronized块结束,或者修饰的方法结束后自动释放锁。

以下是别人的一段话:我觉得比较清晰了

http://zzhonghe.iteye.com/blog/826162

synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。

ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

这个回头咱们可以写一段程序一个用synchronized,一个用ReentrantLock作时间和内存上的测试

8 楼 你若无情我便休 2011-08-21  
问一下lz,这个东西和用synchronized有什么区别吗?
7 楼 OLIVER_kahn111 2011-08-21  
说实话,没看懂,凑凑人气
6 楼 thomas_mule0086 2011-08-20  
snake1987 写道
被分布式诱惑进来的~
建议改下题目~~
实际分布式跟java的并发包没有一毛钱的关系~

你没在构建分布式系统中用到这些并发技术,不代表别人没用过,小盆友,谦虚点。
5 楼 jilen 2011-08-20  
并发就并发,不一定非要套个分布式吧
4 楼 struts23045 2011-08-20  
引用
工作地点:北京

可惜不在北京,催悲了。
3 楼 季铵盐 2011-08-20  
2 楼 snake1987 2011-08-20  
被分布式诱惑进来的~
建议改下题目~~
实际分布式跟java的并发包没有一毛钱的关系~
1 楼 uuid198909 2011-08-19  
终于等到了~

相关推荐

Global site tag (gtag.js) - Google Analytics