论坛首页 Java企业应用论坛

同一任务和对象锁的问题

浏览 13709 次
精华帖 (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的顺序既可以避免死锁。
0 请登录后投票
   发表时间:2010-08-04   最后修改:2010-08-04
对于单线程来说,synchronized锁是可重入的

底层具体实现咱不知道。

但是完全可以这么理解:
只要拥有synchronized 对应的监视器对象(我把这个监视器对象看作是锁),就能进入代码块。

如果还没拥有该监视器对象,那得获取。
如果已经拥有该监视器对象,就不需要重新获取,我认为每个synchronized对应的监视器对象是唯一的,并且同时只能被1个线程所拥有,可能jvm底层实现会有个计数器,但是不搭嘎,并且也对开发来说,也不重要。


平时做开发了解到这一点就可以啦。


这不像lock,lock从源代码上,可以看到是有计数器的。并且readlock是可以多个线程同时持有的。

0 请登录后投票
   发表时间:2010-08-04   最后修改:2010-08-04
asialee 写道
hardPass 写道
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

我一直认为是,已经持有锁了,可以继续执行synchronized方法。
不是你说的“可以再次获得该对象锁”。


应该是采用的是引用计数的方法来实现的课重入。


计数?统计什么?记录下同一个线程 Monitor entered 了几次?
但是,这个计数器有什么用呢?又不是类似readlock的东西,可以多个线程同时持有锁。
而且这个synchronized根本不需要显示的声明释放锁。
所以我认为,即使是jvm底层实现,也没有必要进行计数。


或许吧,可能jvm底层实现,是有计数器,那只是可能,并且与开发人员没有关系,没有必要知道。
0 请登录后投票
   发表时间:2010-08-04  
hardPass 写道
对于单线程来说,synchronized锁是可重入的


重进入是针对多线程的,单线程synchronized还有什么意义?

0 请登录后投票
   发表时间:2010-08-04  
ywlqi 写道
把f1() f2()看作是2个对象,是不是更好解释一些?


貌似这样,会更无法解释。

同一个对象的所有 synchronized方法都是锁的对象本身(this)。

所以只要某个线程拥有了 这个锁,该线程就可以执行所有的synchronized方法
0 请登录后投票
   发表时间:2010-08-04  
hardPass 写道
asialee 写道
hardPass 写道
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

我一直认为是,已经持有锁了,可以继续执行synchronized方法。
不是你说的“可以再次获得该对象锁”。


应该是采用的是引用计数的方法来实现的课重入。


计数?统计什么?记录下同一个线程 Monitor entered 了几次?
但是,这个计数器有什么用呢?又不是类似readlock的东西,可以多个线程同时持有锁。
而且这个synchronized根本不需要显示的声明释放锁。
所以我认为,即使是jvm底层实现,也没有必要进行计数。


或许吧,可能jvm底层实现,是有计数器,那只是可能,并且与开发人员没有关系,没有必要知道。


synchronized方法和块中,都是开辟了“临界区”,重复进入相同的“临界区”,确实需要计数。当进入时+1,退出是-1。
0 请登录后投票
   发表时间: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。
0 请登录后投票
   发表时间:2010-08-04  
synchronized 其实就是编译器在代码段帮你生成了tryLock,unlock指令罢了
0 请登录后投票
   发表时间:2010-08-04  
看了半天原来对象锁是monitor,妈的翻译害死人
0 请登录后投票
   发表时间: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通知机制。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics