Java并发框架提供了外部锁,这些锁类似于进入同步块而获得的固有锁,但具有灵活性并提供其他功能。在本教程中,您可以通过示例学习Lock和ReadWriteLock接口,ReentrantLock和ReentrantReadWriteLock锁实现和条件。
锁具
当线程进入语句或同步方法的同步块时,它获取感兴趣的对象的锁,线程之间存在共享的数据,以避免数据损坏。锁一次仅允许一个线程访问共享资源。但是以这种方式获得的固有锁定具有有限的操作。它不是灵活的,因为只能在它进入一个块时才能获取,并且必须在存在该块时才释放。如果一个线程获取多个锁,则它们必须以相反的顺序释放,这意味着最后获取的锁是要释放的第一个锁,然后是第二个最新的锁,依此类推。
Java并发框架锁提供了广泛的锁定操作并具有灵活性。并发框架中提供的锁实现允许在任何范围内获取和释放锁,这意味着释放锁,不必像固有锁那样遵循顺序。此功能称为“交接”或“链式锁定”,通过该功能,线程可以获取锁A和锁B,然后释放锁A,然后获取锁C,依此类推。
使用此外部锁,线程可以尝试在特定时间段内获取锁,如果在指定的时间段内无法获取锁,则可以退出,或者如果没有锁,则线程可以立即退出。而且,当线程正在等待或尝试获取锁时,如果线程被中断,该操作将不会阻塞该线程。
与内在锁一样,Lock对象可以一次由一个线程拥有,并且可以使用等待和通知机制来实现线程间通信。条件对象提供了一种让线程等待条件改变并被其他线程通知的手段。
锁界面
Java并发框架中的锁接口定义了用于获取不同形式的锁的方法。方法lock()与内部锁定类似,如果无法获取锁定并可能导致死锁,则可以阻止当前线程。
方法lockInterruptible()允许当前线程获取锁(如果可用)并且该线程不被中断。如果锁不可用并且线程没有中断,它将等待直到锁可用。
方法tryLock()允许当前不间断的线程获取锁(如果有)。否则,它将退出或重试,直到指定的时间到期为止,具体取决于tryLock()方法的版本。
方法unlock()释放锁定。最佳做法是将语句块(在获取锁之后执行)保留在try块中,并在finally块中的锁对象上调用unlock方法以释放锁。
ReentrantLock示例
ReentrantLock实现了Lock接口,它类似于内部锁。当前线程通过对其调用锁方法来获得锁。如果锁不可用,线程将阻止等待锁。当多个线程等待锁定时,可以使用公平策略机制通过给它最长的等待线程,使ReentrantLock赋予优先级。具有公平政策的ReentrantLock可以通过将真实值传递给其构造函数来创建。
除了实现在Lock接口中定义的方法外,ReentrantLock还提供诸如getHoldCount(),getOwner(),getQueuedThreads(),isFiar(),isHeldByCurrentThread()和newConditions()之类的方法。
下面的示例演示如何使用ReentrantLock。该示例实例化ReentrantLock对象,并在两个线程中对其调用lock方法。第一个线程获取锁并执行任务,而第二个线程等待锁被释放。在第一个线程执行完毕后,第二个线程获得锁定。在finally块中调用方法unlock来释放锁。要检查当前线程是否获得锁定,可以使用isHeldByCurrentThread()方法。
public static void main(String[] args) {
ReentrantLock rlock = new ReentrantLock(true);
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
rlock.lock();
try {
if(rlock.isHeldByCurrentThread()) {
System.out.println("thread one lock obtained");
Thread.sleep(5000);
}
} catch (InterruptedException e) { }
finally {
if(rlock.isHeldByCurrentThread()) {
rlock.unlock();
}
}
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
rlock.lock();
try {
if(rlock.isHeldByCurrentThread()) {
System.out.println("thread two lock obtained");
}
}finally {
if(rlock.isHeldByCurrentThread()) {
rlock.unlock();
}
}
}
});
threadOne.start();
threadTwo.start();
System.out.println("main thread");
}
输出量
thread one lock obtained
main thread
thread two lock obtained
如果您调用tryLock()方法来获得两个线程中的锁定,则第二个线程将不会阻塞并继续执行。这是使用tryLock()而不是lock()的上述程序的输出
thread one lock obtained
main thread
您可以使线程尝试锁定一定的时间。
try {
rlock.tryLock(5500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
如果在我们的示例中将上述代码添加到第二个线程,则将获得类似于第一个用例的输出。唯一的区别是,如果第二个线程通过在其上调用interrupt()方法而中断,则tryLock将抛出被中断的异常,并且不会阻塞等待锁定的线程。
您也可以使用lockInterruptible()方法获得锁,lock和lockInterruptible方法之间的区别在于,如果当前线程被中断,lockInterruptible方法不会阻塞或不提供锁。
读锁
线程可以使用ReadWriteLock获得读锁定和写锁定。读锁用于只读操作,写锁用于写操作。获得读锁的线程可以查看由先前释放写锁的线程进行的更新。
尽管ReadWriteLock具有读写锁定,但在给定的时间点仅使用一个锁定(读取锁定或写入锁定)。但是,只要没有写程序,多个线程就可以同时拥有读锁。ReadWriteLock可以提高读取次数和写入次数的性能,因为多个线程可以同时拥有读取锁。
ReadWriteLock接口为线程定义了两种获取读和写锁的方法,分别为readLock()和writeLock()。ReentrantReadWriteLock是ReadWriteLock接口的实现。
ReentrantReadWriteLock示例
以下示例显示如何使用ReentrantReadWriteLock使用读写锁。
public class ShoppingCart {
private List<String> products = new ArrayList<String>();
public String getProduct(int i) {
return products.get(i);
}
public void addProduct(String name) {
products.add(name);
}
}
public static void main(String[] args) {
//instantiate share object
ShoppingCart scart = new ShoppingCart();
scart.addProduct("iphone");
//instantiate ReentrantReadWriteLock
ReentrantReadWriteLock rrwlock = new ReentrantReadWriteLock();
Thread threadRead = new Thread(new Runnable() {
@Override
public void run() {
ReadLock rl = rrwlock.readLock();
try {
//obtain read lock, so that no write occur during reading
rl.lock();
//read from shared object
System.out.println(scart.getProduct(0));
Thread.sleep(1000);
} catch (InterruptedException e) { }
finally {
//release read lock
rl.unlock();
}
}
});
Thread threadWrite = new Thread(new Runnable() {
@Override
public void run() {
//get write lock
//lock() block current thread till it acquires write lock
//if you don't want to block current thread, use tryLock()
rrwlock.writeLock().lock();
try {
//if tryLock() is used, check if write lock is obtained
if(rrwlock.isWriteLockedByCurrentThread()) {
//write to shared object
scart.addProduct("pixel");
System.out.println("thread write lock obtained");
Thread.sleep(1000);
}
}catch (InterruptedException e) { }
finally {
if(rrwlock.isWriteLockedByCurrentThread()) {
//release or unlock write lock
rrwlock.writeLock().unlock();
}
}
}
});
threadRead.start();
threadWrite.start();
System.out.println("main thread");
}
通过将真实值传递给其构造函数,可以在公平模式下使用ReentrantReadWriteLock。在公平模式下使用时,ReentrantReadWriteLock将写锁定分配给等待时间最长的线程。如果线程组正在等待读取锁,则它将读取锁分配给该组。在非公平模式下使用时,将不维护授予锁的顺序。
ReentrantReadWriteLock rrwlock = new ReentrantReadWriteLock(true);
ReentrantReadWriteLock支持重入,这意味着获得读取锁定的读取器可以重新获得读取锁定。类似地,获得写锁定的写程序可以重新获得写锁定。而且,获得了写锁的写程序可以获取读锁,但是不允许反向,这意味着允许写到读降级,并且读到写锁不能升级。在编写者调用使用读锁的方法的情况下,重入非常有用。
rrwlock.writeLock().lock();
rrwlock.readLock().lock();
try {
//do something
}finally {
rrwlock.readLock().unlock();
rrwlock.writeLock().unlock();
}
rrwlock.writeLock().lock();
rrwlock.writeLock().lock();
try {
//do something
}finally {
rrwlock.writeLock().unlock();
rrwlock.writeLock().unlock();
}
健康)状况
就像线程在使用内部锁来限制对共享对象的访问时使用对象的wait(),notify()和notifyAll()方法进行通信一样,当使用外部锁时,Condition允许线程间通信。条件定义了挂起线程直到另一个线程通知它的机制。使用条件时,可以使用外部锁避免死锁问题。
条件对象是通过在锁对象ReentrantLock或ReentrantReadWriteLock上调用newCondition()方法获得的。条件定义用于等待和通知的方法,例如await(),signal()和signalAll()。重载的await()方法允许您指定等待的持续时间。方法siganlAll()通知所有等待线程。
条件示例
下面的示例演示在使用ReentrantLock限制访问共享消息对象(其中包含发布和查看消息的方法)时,如何使用条件对象在两个线程之间进行通信。如果未消耗最后一条消息,则发布消息线程将使用条件对象等待并在其上调用awaits()。同样,如果没有新消息,则视图消息线程将使用另一个条件对象等待并在其上调用awaits方法。
当发布消息线程设置新消息时,它将通过在等待视图消息线程等待的条件对象上调用signal()来通知视图消息线程。同样,当查看消息线程使用该消息时,它将通过在等待发布消息线程正在等待的条件对象上调用signal()方法来通知发布消息线程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Message {
final private Lock lock = new ReentrantLock();
final private Condition producedMsg = lock.newCondition();
final private Condition consumedMsg = lock.newCondition();
private String message;
private boolean messageState;
private boolean endIt;
public void viewMessage() {
//lock
lock.lock();
try {
//no new message wait for new message
while (!messageState)
producedMsg.await();
System.out.println("Here is the latest message : "+message);
messageState = false;
//message consumed, notify waiting thread
consumedMsg.signal();
}catch(InterruptedException ie) {
System.out.println("Thread interrupted - viewMessage");
}finally {
lock.unlock();
}
}
public void publishMessage(String message) {
lock.lock();
try {
//last message not consumed, wait for it be consumed
while (messageState)
consumedMsg.await();
System.out.println("adding latest message ");
this.message = message;
messageState = true;
//new message added, notify waiting thread
producedMsg.signal();
}catch(InterruptedException ie) {
System.out.println("Thread interrupted - publishMessage");
}finally {
lock.unlock();
}
}
public boolean isEndIt() {
return endIt;
}
public void setEndIt(boolean endIt) {
this.endIt = endIt;
}
}
消息生产者。
public class MessageProducer implements Runnable {
private Message message;
public MessageProducer(Message msg) {
message = msg;
}
@Override
public void run() {
pusblishMessages();
}
private void pusblishMessages(){
List<String> msgs = new ArrayList<String>();
msgs.add("hello");
msgs.add("current project is complete");
msgs.add("here is the estimation for new project");
for(String msg : msgs) {
message.publishMessage(msg);
try {
Thread.sleep(400);
} catch (InterruptedException e) {}
}
message.publishMessage("bye");
message.setEndIt(true);
}
}
邮件查看器。
public class MessageViewer implements Runnable{
private Message message;
public MessageViewer(Message msg) {
message = msg;
}
@Override
public void run() {
while(!message.isEndIt())
message.viewMessage();
}
}
启动两个线程。
public static void main(String[] args) {
Message msg = new Message();
Thread messageProducer = new Thread(new MessageProducer(msg));
Thread messageViewer = new Thread(new MessageViewer(msg));
messageProducer.start();
messageViewer.start();
}