Locks in Java

Locks in Java

锁也是一种像同步块一样的线程同步机制,但是锁可能比Java的同步块更复杂。 锁(以及其他更高级的同步机制)是使用synchronized块创建的,因此我们不能完全摆脱synchronized关键字。

从Java 5开始,包java.util.concurrent.locks包含几个锁实现,因此您可能不必实现自己的锁。 但是你仍然需要知道如何使用它们,知道它们背后的实现理论仍然是有用的。 有关更多详细信息,请参阅我的java.util.concurrent.locks.Lock接口教程。

A Simple Lock

让我们从一个同步块开始看起:

public class Counter {
	
	private int count = 0;

	public int inc() {
		synchronized(this) {
			return ++count;
		}
	}
}

仅仅是一个++count操作, 却使用上了同步块…

相反,使用Lock而不是synchronized块,Counter类可以这样编写:

public class Counter {
	
	private Lock lock = new Lock();
	private int count = 0;

	public int inc() {
		lock.lock();
		int newCount = ++count;
		lock.unlock();
		return newCount;
	}
}

lock()方法锁定Lock实例,以便阻止所有调用lock()的线程,直到执行unlock()。

这是一个简单的上述Lock实现:

public class Lock {
	
	private boolean isLocked = false;

	public synchronized void lock()	throws InterruptedException {
		// 这里使用while而不是if,应该是为了防止虚假唤醒
		while(isLocked) {
			this.wait();
		}
		isLocked = true;
	}
	
	public synchronized void unlock() {
		isLocked = false;
		notify();
	}
}

当线程完成关键部分中的代码(lock()和unlock()之间的代码)时,线程调用unlock()。 执行unlock()设置isLocked回到false,并通知(唤醒)在lock()方法中等待wait()调用的其中一个线程(如果有)。

Lock Reentrance

ava中的同步块是可重入的。 这意味着,如果Java线程进入同步的代码块,从而对监视器对象的锁定进行同步,则该线程可以重入在同一监视器对象上同步的其他Java代码块。 这是一个例子:

public class Reentrance {
	
	public synchronized outer() {
		inner();
	}

	public synchronized inner() {
		// do something
	}
}

注意outer()和inner()是如何声明同步的,在Java中它等同于synchronized(this)块。 如果一个线程调用outer(),则从outer()内部调用inner()没有问题,因为两个方法(或块)在同一个监视器对象(“this”)上同步。 如果线程已经在监视器对象上持有锁,则它可以访问在同一监视器对象上同步的所有块。 这称为重入。 线程可以重入已经拥有锁的任何代码块。

前面显示的锁实现不可重入。 如果我们像下面那样重写Reentrant类,则调用outer()的线程将在inner()方法中的lock.lock()内被阻塞。

public class Reentrant2 {
	
	Lock lock = new Lock();

	public void outer() {
		lock.lock();
		inner();
		lock.unlock();
	}

	public void inner() {
		lock.lock();
		//do something
		lock.unlock();
	}
}

调用outer()的线程将首先锁定Lock实例。 然后它将调用inner()。 在inner()方法内部,线程将再次尝试锁定Lock实例。 这将失败(意味着线程将被阻止),因为Lock实例已经在outer()方法中被锁定。

调用outer()的线程将首先锁定Lock实例。 然后它将调用inner()。 在inner()方法内部,线程将再次尝试锁定Lock实例。 这将失败(意味着线程将被阻止),因为Lock实例已经在outer()方法中被锁定。

public class Lock {
	
	boolean isLocked = false;

	public synchronized void lock() throws InteruptedException {
		while(isLocked) {
			wait();
		}
		isLocked = true;
	}
	
	...
}

while循环(自旋锁)中的条件确定是否允许线程退出lock()方法。 目前的条件是isLocked必须为false才允许这样做,无论什么线程锁定它。

要使Lock类可重入,我们需要进行一些小改动:

public class Lock {

	boolean isLocked = false;
	Thread lockedBy = null;
	int lockedCount = 0;

	public synchronized void lock() throws InteruptedException {
		Thread callingThread = Thread.currentThread();
		while(isLocked && lockedBy != callingThread) {
			wait();
		}
		isLocked = true;
		lockedCount++;
		lockedBy = callingThread;
	}
	
	public synchronized void unlock() {
		if(Thread.currentThread() == this.lockedBy) {
			lockedCount--;
			if(lockedCount == 0) {
				isLocked = false;
				notify();
			}
		}
	}

	...
}

注意while循环(自旋锁)现在也如何考虑锁定Lock实例的线程。 如果锁被解锁(isLocked = false)或者调用线程是锁定Lock实例的线程,则while循环将不会执行,并且将允许调用lock()的线程退出该方法。

此外,我们需要计算锁被同一个线程锁定的次数。 否则,单次调用unlock()将解锁锁,即使锁被多次锁定也是如此。 我们不希望锁被解锁,直到锁定它的线程执行了与lock()调用相同数量的unlock()调用。

Lock类现在是可重入的了。

Lock Fairness

Calling unlock() From a finally-clause

使用Lock保护关键部分时,临界区可能会抛出异常,因此从finally子句中调用unlock()方法非常重要。 这样做可确保锁定已解锁,以便其他线程可以锁定它。 这是一个例子:

lock.lock();
try {
	// do critical section code, which may throw exception.
} finally {
	lock.unlock();
}

这个小构造确保锁定被解锁,以防从关键部分的代码中抛出异常。 如果未从finally子句内部调用unlock(),并且从临界区抛出异常,则Lock将永远保持锁定状态,从而导致在该Lock实例上调用lock()的所有线程无限制地停止。

翻译自: http://tutorials.jenkov.com/java-concurrency/locks.html
原作者: Jakob Jenkov
Orz Orz Orz

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值