Java并发锁和条件

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();

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值