锁定老帖子 主题:同一任务和对象锁的问题
精华帖 (0) :: 良好帖 (1) :: 新手帖 (9) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-08-04
maozj 写道 偶尔翻开java编程思想看看多线程的篇章,意焦突然卡在某个问题上不动了。这个问题看过多少次多少遍了,此刻才领会,顿时感觉自己好笨拙的思维。
问题是这样的:一般来说,在多线程程序中,某个任务在持有某对象的锁后才能运行任务,其他任务只有在该任务释放同一对象锁后才能拥有对象锁,然后执行任务。于是,想到,同一个任务在持有同一个对象的锁后,在不释放锁的情况下,继续调用同一个对象的其他同步(synchronized)方法,该任务是否会再次持有该对象锁呢? 答案是肯定的。同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。 多线程编程是一件很微妙的事情,这里考验人的不是大局而是细节,细节的细节。希望能唤醒自己愚笨的思维。 package thread.thread1; /** * 同一任务可以再次持有对象锁 create on 2010.08.04 08:27 * * @since jdk1.6 * @author maozj * @version 1.0 * */ public class SynchronizedClassHolder { /** * Test client * @param args */ public static void main(String[] args) { new Thread() { public void run() { new SynchronizedClassHolder().f1(); } }.start(); } private static int count = 10; /** * synchronized f1() */ public synchronized void f1() { if (--count > 0) { System.out.println("f1() calling f2() count " + count); f2(); } } /** * synchronized f2() */ public synchronized void f2() { if (--count > 0) { System.out.println("f2() calling f1() count " + count); f1(); } } } 输出: f1() calling f2() count 9 f2() calling f1() count 8 f1() calling f2() count 7 f2() calling f1() count 6 f1() calling f2() count 5 f2() calling f1() count 4 f1() calling f2() count 3 f2() calling f1() count 2 f1() calling f2() count 1 这个很简单的啊,因为Java并发(synchronized或者Lock#lock())是“重进入”的(Reentrance)。楼主的例子中,Monitor对象皆是this(f1和f2),因此在方法进入(进栈)时,Reentrance计数器count加1,方法返回(出栈)后,Reentrance计数器count减1。如果最终count==0的话,那么不会出现死锁。 建议楼主看一下java.util.concurrent.ReentrantLock的实现,它的lock()方法即计数+1,unlock()方法即计数减1。 因此需要成对出现,否则死锁。synchronized,注意monitor的顺序既可以避免死锁。 |
|
返回顶楼 | |
发表时间:2010-08-04
最后修改:2010-08-04
对于单线程来说,synchronized锁是可重入的
底层具体实现咱不知道。 但是完全可以这么理解: 只要拥有synchronized 对应的监视器对象(我把这个监视器对象看作是锁),就能进入代码块。 如果还没拥有该监视器对象,那得获取。 如果已经拥有该监视器对象,就不需要重新获取,我认为每个synchronized对应的监视器对象是唯一的,并且同时只能被1个线程所拥有,可能jvm底层实现会有个计数器,但是不搭嘎,并且也对开发来说,也不重要。 平时做开发了解到这一点就可以啦。 这不像lock,lock从源代码上,可以看到是有计数器的。并且readlock是可以多个线程同时持有的。 |
|
返回顶楼 | |
发表时间:2010-08-04
最后修改:2010-08-04
asialee 写道 hardPass 写道 maozj 写道 同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。
是这样的吗? 我一直认为是,已经持有锁了,可以继续执行synchronized方法。 不是你说的“可以再次获得该对象锁”。 应该是采用的是引用计数的方法来实现的课重入。 计数?统计什么?记录下同一个线程 Monitor entered 了几次? 但是,这个计数器有什么用呢?又不是类似readlock的东西,可以多个线程同时持有锁。 而且这个synchronized根本不需要显示的声明释放锁。 所以我认为,即使是jvm底层实现,也没有必要进行计数。 或许吧,可能jvm底层实现,是有计数器,那只是可能,并且与开发人员没有关系,没有必要知道。 |
|
返回顶楼 | |
发表时间:2010-08-04
hardPass 写道 对于单线程来说,synchronized锁是可重入的
重进入是针对多线程的,单线程synchronized还有什么意义? |
|
返回顶楼 | |
发表时间:2010-08-04
ywlqi 写道 把f1() f2()看作是2个对象,是不是更好解释一些?
貌似这样,会更无法解释。 同一个对象的所有 synchronized方法都是锁的对象本身(this)。 所以只要某个线程拥有了 这个锁,该线程就可以执行所有的synchronized方法 |
|
返回顶楼 | |
发表时间:2010-08-04
hardPass 写道 asialee 写道 hardPass 写道 maozj 写道 同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。
是这样的吗? 我一直认为是,已经持有锁了,可以继续执行synchronized方法。 不是你说的“可以再次获得该对象锁”。 应该是采用的是引用计数的方法来实现的课重入。 计数?统计什么?记录下同一个线程 Monitor entered 了几次? 但是,这个计数器有什么用呢?又不是类似readlock的东西,可以多个线程同时持有锁。 而且这个synchronized根本不需要显示的声明释放锁。 所以我认为,即使是jvm底层实现,也没有必要进行计数。 或许吧,可能jvm底层实现,是有计数器,那只是可能,并且与开发人员没有关系,没有必要知道。 synchronized方法和块中,都是开辟了“临界区”,重复进入相同的“临界区”,确实需要计数。当进入时+1,退出是-1。 |
|
返回顶楼 | |
发表时间:2010-08-04
hardPass 写道 ywlqi 写道 把f1() f2()看作是2个对象,是不是更好解释一些?
貌似这样,会更无法解释。 同一个对象的所有 synchronized方法都是锁的对象本身(this)。 所以只要某个线程拥有了 这个锁,该线程就可以执行所有的synchronized方法 冒失这里理解错误了,线程第一个遇到synchronized关键字的时候,就获得了锁。至于monitor对象,是用于消息通知机制-wait/notify的语义。 synchronized(a){ synchronized(b){ } }[/code a和b都在一个获得(锁)中,但是a和b是不同的monitor,当a.wait()阻塞获得锁的线程t1时,b.notify()的调用不会唤起线程t1。 |
|
返回顶楼 | |
发表时间:2010-08-04
synchronized 其实就是编译器在代码段帮你生成了tryLock,unlock指令罢了
|
|
返回顶楼 | |
发表时间:2010-08-04
看了半天原来对象锁是monitor,妈的翻译害死人
|
|
返回顶楼 | |
发表时间:2010-08-04
mercyblitz 写道 hardPass 写道 asialee 写道 hardPass 写道 maozj 写道 同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。
是这样的吗? 我一直认为是,已经持有锁了,可以继续执行synchronized方法。 不是你说的“可以再次获得该对象锁”。 应该是采用的是引用计数的方法来实现的课重入。 计数?统计什么?记录下同一个线程 Monitor entered 了几次? 但是,这个计数器有什么用呢?又不是类似readlock的东西,可以多个线程同时持有锁。 而且这个synchronized根本不需要显示的声明释放锁。 所以我认为,即使是jvm底层实现,也没有必要进行计数。 或许吧,可能jvm底层实现,是有计数器,那只是可能,并且与开发人员没有关系,没有必要知道。 synchronized方法和块中,都是开辟了“临界区”,重复进入相同的“临界区”,确实需要计数。当进入时+1,退出是-1。 补充一点,如果synchronized定义的方法是对象方法,哪么锁就在加载this对象上。如果是类方法的话,锁就在该类之上。所谓重进入就是从第一个synchronized开始,不断地进入了synchronized块或者方法,尽管synchronized可以监视不同的monitor。而monitor是用于wait/notify通知机制。 |
|
返回顶楼 | |