synchronized关键字和监视器

本文深入解析Java中的同步机制,重点介绍synchronized关键字的使用及其实现原理,包括内部锁、同步方法与代码块,以及与ReentrantLock的对比。同时,文章探讨了监视器的概念及其在Java中的应用。

Lock和Condition接口为我们提供了高度的锁定机制,然而,大多数情况下,并不需要那样的控制,我们可以选择一种更简洁的方式——使用一种嵌入到Java语言内部的机制,synchronized关键字。

内部锁

要理解并使用synchronized关键字,必须首先知道:从Java 1.0版本开始,Java中的每一个对象都有一个内部锁。

内部锁是存在Java对象头里的一部分数据。如果对象是数组类型,则虚拟机用3个字宽存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit。如下所示:
在这里插入图片描述
如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法,要调用该方法,线程必须先获得内部的对象锁。

使用synchronized关键字实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为:
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步代码块,锁是synchronized括号里的对象。

同步方法

用synchronized关键字声明方法,表示该方法是同步的,任何时刻下只允许一个线程调用该方法。

public synchronized void method () {
Method body
}

内部锁只有一个相关条件。当进入临界区的线程未满足执行的条件,则调用wait方法阻塞该线程,其他的线程执行了可能使阻塞的线程满足执行条件的操作时,应该调用notifyAll/notify方法解除线程的阻塞。

注意:wait、notifyAll及notify是Object类的final方法。

例如我们可以将之前的模拟银行转账例子中的Bank类修改成其方法使用synchronized声明来保证同步。

public class Bank {
/**
 * 转账
 */
	public synchronized boolean transfer(Account fromAccount, Account toAccount, double money) throws InterruptedException {
		while (fromAccount.getMoney() < money) 
			wait();
		fromAccount.setMoney(fromAccount.getMoney() - money);
		toAccount.setMoney(toAccount.getMoney() + money);
		System.out.println(fromAccount.getName() + "向" + toAccount.getName() + "转账" + money + "元");
		notifyAll();
		return true;
	}

	/**
	 * 打印余额
	 * 
	 * @param account
	 *            账户
	 */
	public synchronized void display(Account account) {
		System.out.println(account.getName() + ":" + account.getMoney() + "元");
	}
}

同步代码块

我们除了使用synchronized声明的同步方法来保证同步外,还可以使用同步代码块。

synchronized (obj){
Critical section
}

例如我们也可以将Bank类修改成如下形式:

public class Bank {
	
	private Object lock = new Object();
	
	/**
	 * 转账
	 */
	public boolean transfer(Account fromAccount, Account toAccount, double money) throws InterruptedException {
		synchronized (lock) {
			fromAccount.setMoney(fromAccount.getMoney() - money);
			toAccount.setMoney(toAccount.getMoney() + money);
			System.out.println(fromAccount.getName() + "向" + toAccount.getName() + "转账" + money + "元");
			return true;
		}

	}

	/**
	 * 打印余额
	 */
	public void display(Account account) {
		synchronized (lock) {
			System.out.println(account.getName() + ":" + account.getMoney() + "元");
		}
	}
}

但是注意wait只能在同步方法中使用,notifyAll及notify方法可以在同步方法或同步块中使用。

synchronized关键字实现原理

synchronized在JVM中的实现原理在于:JVM基于进入和退出Monitor(监视器)对象来实现方法的同步和代码块的同步,但两者的实现细节不一样。

代码块的同步是使用monitorenter和monitorexit指令实现的,虽然方法的同步使用另外一种方式实现,但是同样也可以使用这两个指令来实现。

monitorenter指令是在编译后插入当同步代码块的开始位置,而monitorexit则是插入到方法结束处和异常处,JVM必须保证每个monitorenter有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,并且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

是不是发现与ReentrantLock对象的使用很相似。

public synchronized void method () {
Method body
}

等价于

public void method(){
this.intrinsicLock.lock();
try {
Method body
}finally {
this.intrinsicLock.unlock();
}
}

监视器

锁和条件是线程同步的强大工具,但是,严格的讲,它们不是面向对象的(没有对加锁、解锁进行封装)。而监视器(monitor)可以在不需要程序员考虑如何加锁的情况下,就可以保证多线程的安全性。
在这里插入图片描述
监视器具有如下特性:
1、监视器是只包含私有域的类;
2、每个监视器类的对象有一个相关的锁;
3、使用该锁对所有的方法进行加锁,对象的锁是在方法的调用开始时自动获得,并且在方法返回时自动释放该锁。
4、该锁可以有任意多个相关条件。

Java对象不同于监视器,不能保证线程的安全性:
1、域不要求必须是private;
2、方法不要求必须是synchronized;
3、内部锁对客户是可用的。

监视器的概念是Per Brinch Hansen提出的,Java采用了监视器的概念。Java中每一个对象有一个内部的锁和内部的条件。如果一个方法用synchronized关键字声明,那么它表现得像是一个监视器方法。通过wait/notifyAll/notify来访问条件变量。

### synchronized关键字与ReentrantLock的区别及使用场景 #### 一、基本概念对比 `synchronized` 是 Java 中内置的关键字,用于提供一种简单的同步机制来控制多个线程对共享资源的访问[^1]。它通过隐式的监视器(Monitor Lock)实现线程安全。 相比之下,`ReentrantLock` 是 `java.util.concurrent.locks.Lock` 接口的一个具体实现类,属于显式的一种。它的设计更加灵活,允许开发者手动获取释放[^2]。 --- #### 二、功能特性差异 ##### 1. **的获取方式** - `synchronized` 的是自动管理的,当线程进入同步代码块或方法时会自动加,在退出时自动解。 - 而 `ReentrantLock` 则需要调用 `lock()` 方法显式加,并在完成后调用 `unlock()` 手动释放[^3]。 ##### 2. **中断支持** - `synchronized` 不支持响应线程中断操作;如果一个线程正在等待,则无法被强制唤醒。 - `ReentrantLock` 提供了带超时尝试获取的方法(如 `tryLock(long timeout, TimeUnit unit)`),并能处理线程中断情况。 ##### 3. **公平性选项** - 默认情况下,`synchronized` 并不区分是否有优先级更高的请求者,采用的是非公平策略。 - 对于 `ReentrantLock` ,可以通过构造函数指定是否启用公平模式(Fairness Policy)。设置为 true 后,遵循 FIFO 秩序分配给等待时间最长的那个线程[^3]。 ##### 4. **性能表现** 随着 JDK 版本的发展,两者之间的性能差距逐渐缩小甚至反转。早期版本中由于 JVM 层面优化不足,通常认为 `ReentrantLock` 性能优于 `synchronized` 。然而现代 JVM 已经极大提升了后者效率,在大多数简单应用场景下两者的开销相差无几。 ##### 5. **额外功能扩展** 除了基础定外,`ReentrantLock` 还具备更多高级特性比如条件队列(`Condition`)对象创建能力等。 --- #### 三、适用范围分析 | 方面 | synchronized | ReentrantLock | |---------------------|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| | 易用程度 | 更简洁直观 | 较复杂 | | 可控性强弱 | 功能有限 | 高度可控 | | 是否支持超时 | 不支持 | 支持 | | 公平 | 自然是非公平 | 用户可以选择 | | 异常安全性 | 如果发生异常也会正常释放 | 若未正确配对 lock/unlock 将导致死 | 对于那些只需要基本互斥保护的小型程序或者框架内部已经封装好的部分来说,推荐直接利用 `synchronized` 来减少维护成本以及潜在错误风险[^4]; 当遇到较为复杂的业务逻辑需求诸如精确控制行为、应对长时间阻塞等问题时则更适合选用 `ReentrantLock`. --- ```java // 使用synchronized的例子 public class Counter { private int count; public synchronized void increment() { this.count++; } } // 使用ReentrantLock的例子 import java.util.concurrent.locks.ReentrantLock; public class CounterWithLock { private final ReentrantLock lock = new ReentrantLock(); private int count; public void increment() { lock.lock(); // 加 try { this.count++; } finally { lock.unlock(); // 解 } } } ``` ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值