synchronized关键字和监视器

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来访问条件变量。

猜你喜欢

转载自blog.csdn.net/is_Javaer/article/details/84840552