`

同一任务和对象锁的问题

阅读更多
   偶尔翻开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
分享到:
评论
17 楼 rain2005 2010-08-04  
synchronized 其实就是编译器在代码段帮你生成了tryLock,unlock指令罢了
16 楼 mercyblitz 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。
15 楼 mercyblitz 2010-08-04  
hardPass 写道
asialee 写道
hardPass 写道
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

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


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


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


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


synchronized方法和块中,都是开辟了“临界区”,重复进入相同的“临界区”,确实需要计数。当进入时+1,退出是-1。
14 楼 hardPass 2010-08-04  
ywlqi 写道
把f1() f2()看作是2个对象,是不是更好解释一些?


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

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

所以只要某个线程拥有了 这个锁,该线程就可以执行所有的synchronized方法
13 楼 mercyblitz 2010-08-04  
hardPass 写道
对于单线程来说,synchronized锁是可重入的


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

12 楼 hardPass 2010-08-04  
asialee 写道
hardPass 写道
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

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


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


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


或许吧,可能jvm底层实现,是有计数器,那只是可能,并且与开发人员没有关系,没有必要知道。
11 楼 hardPass 2010-08-04  
对于单线程来说,synchronized锁是可重入的

底层具体实现咱不知道。

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

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


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


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

10 楼 mercyblitz 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的顺序既可以避免死锁。
9 楼 dennis_zane 2010-08-04  
简单来说,对象锁是可重入的,可重入针对的是每线程。
8 楼 ywlqi 2010-08-04  
把f1() f2()看作是2个对象,是不是更好解释一些?
7 楼 melin 2010-08-04  
每个对象有一个监视器对象,一个线程获取到该监视器以后。其它线程就只能进入该对象监视器的等待队列中,等待线程处于等待运行状态,等获取监视器的线程释放锁以后,从等待队列中激活一线程进行运行。。。

如果两个不同的同步方法,没有共享相同状态信息,考虑使用不同的监视器对象,也就是所说的拆分锁
6 楼 maozj 2010-08-04  
hardPass 写道
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

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


-------------------
呵呵 你一直认为?
5 楼 asialee 2010-08-04  
hardPass 写道
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

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


应该是采用的是引用计数的方法来实现的课重入。
4 楼 hardPass 2010-08-04  
maozj 写道
同一个任务在调用同一个对象上的其他synchronized方法,可以再次获得该对象锁。


是这样的吗?

我一直认为是,已经持有锁了,可以继续执行synchronized方法。
不是你说的“可以再次获得该对象锁”。
3 楼 donliue 2010-08-04  
嗯。深入java虚拟机中说:一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减1,当计数器值为0时,锁就被完全释放了。
2 楼 michael.softtech 2010-08-04  
<p>我是这样理解的。 Java Monitors Are Reentrant 也就是当前线程如果已经有了一个Object a的锁,那么此线程想再次获取这个a的锁时,可以继续获取。然后在此线程对于该锁的计数上+1. 比如</p>
<p> </p>
<pre name="code" class="java">synchronized  m1(){
//加入此时对锁a的计数是N
m2();  //进入m2的方法体之后锁计数是N+1,离开m2后是N
}
synchronized m2(){}</pre>
<p> 这样就能保证锁的正确获取和释放了。</p>
<p> </p>
<p> 个人理解,仅供参考~</p>
1 楼 hatedance 2010-08-04  
期待高手讲解原理

相关推荐

    java多线程的条件对象和锁对象demo

    本示例"java多线程的条件对象和锁对象demo"着重探讨了如何利用锁对象和条件对象来精细控制线程的执行流程。 首先,我们需要了解Java中的锁对象。Java提供了多种类型的锁,其中最基础的是`synchronized`关键字,它...

    快速查找oracle锁对象

    在Oracle数据库管理中,"快速查找Oracle锁对象"是一个关键任务,特别是在处理并发事务和解决性能问题时。当多个用户或进程同时访问同一资源时,可能会出现锁冲突,导致某些事务等待,影响数据库的正常运行。了解如何...

    oracle由于对象被锁住无法编译处理

    总之,“由于对象被锁住无法编译处理”的问题虽然常见,但通过合理利用Oracle提供的工具和适当的应用程序设计,大多数情况下都是可以有效管理和解决的。在处理这类问题时,保持耐心,仔细分析,并采取适当的措施是...

    使用redis做任务队列分发子任务

    3. 广播与订阅:通过发布/订阅模式,可以实现任务的广播,让多个消费者同时处理同一任务,提高任务处理能力。 4. 弹性扩展:随着任务量的增长,可以轻松添加更多工作节点来消费队列中的任务。 5. 锁机制:Redis提供...

    易语言线程互斥对象解决

    线程互斥对象允许我们限制对共享资源的访问,防止多个线程同时访问同一资源,从而避免数据竞争和不一致状态。 1. **线程互斥对象(Mutex)**: 线程互斥对象是一种同步机制,当一个线程获得了Mutex的所有权后,...

    从0开始开发 基础库(配置文件读写、日志、多线程、多进程、锁、对象引用计数、内存池、免锁消息队列、免锁数据缓冲区、进.zip

    本压缩包“从0开始开发 基础库”涵盖了多个重要的编程概念和技术,包括配置文件读写、日志记录、多线程与多进程操作、同步机制如锁、对象引用计数、内存池以及两种优化数据结构——免锁消息队列和免锁数据缓冲区。...

    vc++中的线程锁(线程锁保持线程同步)

    线程锁就是用来解决这个问题的一种工具,它允许我们限制对特定资源的访问,确保在同一时间只有一个线程可以访问和修改该资源。 VC++中实现线程锁的方法多种多样,包括临界区(Critical Section)、互斥量(Mutex)...

    oracle锁表查询oracle锁表查询oracle锁表查询

    通过锁机制,Oracle能够确保数据的一致性和完整性,避免多用户操作时可能出现的数据冲突问题。 #### 二、锁表查询方法 ##### 1. 使用`V$LOCKED_OBJECT`视图进行锁表查询 在Oracle数据库中,可以通过查询`V$LOCKED...

    多线程同时查询同一数据库对比

    在这个场景下,我们将比较单线程和多线程查询同一数据库的性能差异。 首先,单线程是指程序按照顺序执行任务,一次只有一个任务在运行。在查询数据库时,如果使用单线程,那么每次只能进行一个查询操作,即使数据库...

    SQL server锁的机制

    SQL Server的并发控制策略基于行级锁和页级锁,允许在一定程度上的并行操作。然而,当写操作与读操作冲突时,锁机制会确保数据的一致性,可能会导致阻塞。例如,一个连接在修改数据时,其他连接会被阻止读取或修改...

    并发编程面试题汇总.docx并发编程是指在一个程序中同时执行多个独立的任务或操作的能力 在面试中,常常会问到与并发编程相关的问题

    - **字节码指令**:被`synchronized`修饰的代码块会在编译后生成`monitorenter`和`monitorexit`字节码指令,用于控制对象锁的获取和释放。 - **对象锁**:`Synchronized`通过对象锁来控制对共享资源的访问。 - **...

    RTT-Mini-mutex.rar

    - **非递归**:常规的互斥锁支持递归,即同一个任务可以多次获取同一把锁而不阻塞自己。但这个简易互斥锁不允许,如果一个任务已经持有了锁,再次尝试获取将导致错误。 3. **简易互斥锁的实现细节** - **数据结构...

    基于C++的任务管理器代码.zip

    多态则允许不同的对象对同一消息做出不同的响应,提供更强大的灵活性。 - **模板与泛型编程**:C++的模板可以创建泛型函数和类,使得代码更具通用性,适应不同数据类型的处理。 2. **进程管理** - **进程状态**:...

    一家三口共用同一账户银行卡,wait();notify();

    当一个线程调用wait()时,它会释放对象锁并进入等待状态,直到其他线程调用同一对象的notify()或notifyAll()方法唤醒它。notify()只会唤醒一个等待的线程,而notifyAll()会唤醒所有等待的线程。 3. **synchronized ...

    等待机制与锁机制wait notify

    需要注意的是,`wait()`、`notify()`和`notifyAll()`的调用者应当是持有对象锁的线程,否则会出现异常。此外,为了防止死锁和饥饿现象,合理地设计同步策略和唤醒逻辑至关重要。 总结来说,`wait`、`notify`和`...

    多线程使用同一数组测试

    例如,在VB.NET中,可以将数组作为锁对象,确保同一时间只有一个线程能够访问它。 ```vbnet SyncLock myArray ' 这里进行数组操作 End SyncLock ``` 2. **线程局部存储(Thread Local Storage)**:如果可能,...

    基于分布式锁或xxx-job实现分布式任务调度.zip

    分布式锁是确保在分布式环境中同一时刻只有一个实例执行特定任务的关键技术。它可以防止并发操作导致的数据不一致性和资源争抢。本项目可能使用了如ZooKeeper、Redis或Apache Curator等工具来实现分布式锁。这些工具...

    浅谈iOS中的锁的介绍及使用

    在示例中,`@synchronized(cjobj)` 创建了一个基于 `cjobj` 对象的锁,两个并发的异步任务会根据这个锁进行同步。如果两个任务使用了不同的锁标识(如将 `cjobj` 更改为 `self`),则不会产生阻塞效果,因为它们实际...

    Java 同步锁 wait notify 学习心得

    `notify()`方法随机唤醒正在等待该对象锁的线程之一。被唤醒的线程将有机会重新获取锁并继续执行。然而,`notify()`并不保证被唤醒的线程会立即执行,因为线程调度取决于操作系统的线程调度策略。 #### `notifyAll...

    [PHP] 基于redis的分布式锁防止高并发重复请求.docx

    * 可重入性:同一对象(如线程、类)可以重复、递归调用该锁而不发生死锁; * 可阻塞:在没有获得锁之前,只能阻塞等待直至获得锁; * 高可用:哪怕发生程序故障、机器损坏,锁仍然能够得到被获取、被释放; * 高...

Global site tag (gtag.js) - Google Analytics