StampedLock类的高级锁

与Lock或者ReadWriteLock接口提供的锁机制都不同,StampedLock类提供了一种特殊的锁。事实上,StampedLock类不实现这些接口,但其提供的功能相似。

首先要注意的是这种锁的主要目的是作为帮助类实现线程安全组件,所以在标准的应用中并不多见。

StampedLock锁最重要的特性如下所示:

  • 三种不同的模式获得锁控制:
    • **写入:**这种模式下,独占锁的使用权,其它线程均无法控制此锁。
    • **读取:**这种模式下,非独占锁的使用权,其它线程在此模式或者乐观读取模式下都可以使用此锁。
    • **乐观读取:**线程无法控制程序块,其它线程在写入模式下能够控制锁。当在乐观读取模式下得到锁,并且想要访问此锁保护的共享数据时,检查是否具有访问权,或者不使用validate()方法。
  • StampedLock类提供的方法用来:
    • 使用这三种模式得到锁控制权。如果方法(readLock(),writeLock(),readLockInterruptibly())无法控制锁,当前线程将被暂停直到方法得到锁。
    • 使用这三种模式得到锁控制权。如果方法(tryOptimisticRead(),tryReadLock(),tryWriteLock())无法控制锁,则返回代表这种情形的特殊值。
    • 进行模式转换,如果可行的话。如果不能进行模式转换,方法(asReadLock(),asWriteLock(),asReadWriteLock())会返回特殊值。
    • 释放锁。
  • 所有方法返回称之为标记的长整型值,用来对锁起作用。如果返回值为零,意味着此方法无法得到锁。
  • StampedLock锁不是像Lock和ReadWriteLock接口实现的重入锁。如果调用这些方法尝试再次得到锁,线程可能会被阻断并且形成死锁。
  • StampedLock锁没有所有权的概念,一个线程能够控制它们,同时被其它线程释放。
  • 最后,对后面将要控制锁的线程不设约束。

在本章中,学习如何使用StampedLock类的不同模式保护访问共享数据对象。在三个并发任务中使用共享对象,来测试StampedLock的三种访问模式(写入、读取、乐观读取)。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 首先,实现共享数据对象。创建名为Position的类,包括两个名为x和y的整型属性,以及属性值的读写方法。代码很简单不在这里显示。

  2. 实现Writer任务,实现Runnable接口,包含两个属性:名为position的Position对象,名为lock的StampedLock对象,并在构造函数中进行初始化:

    public class Writer implements Runnable{
    
    	private final Position position;
    	private final StampedLock lock;
    	
    	public Writer (Position position, StampedLock lock) {
    		this.position = position;
    		this.lock = lock;
    	}
    
  3. 实现run()方法,在写入模式中得到锁,改变位置对象的两个属性值,中断线程执行1秒钟,释放锁(在任何情形下使用try…catch…finally结构,将代码置于finally部分来释放锁),然后再中断线程一秒钟。在循环中重复上述过程10次:

    	@Override
    	public void run() {
    		for(int i = 0 ; i < 10; i ++){
    			long stamp = lock.writeLock();
    			
    			try {
    				System.out.printf("Writer : Lock acquired %d\n", stamp);
    				position.setX(position.getX() + 1);
    				position.setY(position.getY() + 1);
    				TimeUnit.SECONDS.sleep(1);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			} finally {
    				lock.unlockWrite(stamp);
    				System.out.printf("Writer : Lock released %d\n", stamp);
    			}
    			
    			try {
    				TimeUnit.SECONDS.sleep(1);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
  4. 然后,实现Reader任务,读取共享对象的值。创建名为Reader的类并实现Runnable接口,包含两个属性:名为position的Position对象,名为lock的StampedLock对象,并在构造函数中进行初始化:

    public class Reader implements Runnable{
    
    	private final Position position;
    	private final StampedLock lock;
    	
    	public Reader(Position position, StampedLock lock){
    		this.position = position;
    		this.lock = lock;
    	}
    
  5. 实现run()方法,在读取模式中得到锁,在控制台中输出位置对象的值,并且中断线程200毫秒,最后使用try…catch…finally结构中的finally里置入代码来释放锁。在循环中重复上述过程50次:

    	@Override
    	public void run() {
    		for(int i = 0 ; i < 50 ; i ++){
    			long stamp = lock.readLock();
    			try {
    				System.out.printf("Reader : %d - (%d, %d)\n", stamp, position.getX(), position.getY());
    				TimeUnit.MILLISECONDS.sleep(200);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			} finally {
    				lock.unlockRead(stamp);
    				System.out.printf("Reader : %d - Lock released\n" , stamp);
    			}
    		}
    	}
    
  6. 然后,实现OptimisticReader任务,实现Runnable接口,包含两个属性:名为position的Position对象,名为lock的StampedLock对象,并在构造函数中进行初始化:

    public class OptimisticReader implements Runnable {
    
    	private final Position position;
    	private final StampedLock lock;
    	public OptimisticReader(Position position, StampedLock lock){
    		this.position = position;
    		this.lock = lock;
    	}
    
  7. 现在实现run()方法,首先使用tryOptimisticRead()方法在乐观读取模式中得到锁的标记。然后,重复循环操作100次。每次循环中,使用validate()方法验证是否能够访问数据,如果方法返回true,在控制台中输出位置对象值,否则,在控制台中输出一条信息并且再次使用tryOptimisticRead()方法得到另一个标记。然后,中断线程200毫秒:

    	@Override
    	public void run() {
    		long stamp;
    		for(int i =0 ; i < 100 ; i++){
    			try {
    				stamp = lock.tryOptimisticRead();
    				int x = position.getX();
    				int y = position.getY();
    				if(lock.validate(stamp)){
    					System.out.printf("OptimisticReader : %d - (%d, %d)\n", stamp, x, y);
    				}else{
    					System.out.printf("OptimisticReader : %d - Not Free\n", stamp);
    				}
    				TimeUnit.MILLISECONDS.sleep(200);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
  8. 最后,实现包含main()方法的Main类,创建Position和StampedLock对象,创建三个执行不同模式任务的线程,开始执行,等待结束:

    public class Main {
    
    	public static void main(String[] args){
    		Position position = new Position();
    		StampedLock lock = new StampedLock();
    		
    		Thread threadWriter = new Thread(new Writer(position, lock));
    		Thread threadReader = new Thread(new Reader(position, lock));
    		Thread threadOptReader = new Thread(new OptimisticReader(position, lock));
    		
    		threadWriter.start();
    		threadReader.start();
    		threadOptReader.start();
    		
    		try {
    			threadWriter.join();
    			threadReader.join();
    			threadOptReader.join();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

工作原理

本范例中,通过使用标记锁测试三种模式。在Writer任务中,使用writeLock()方法得到锁(需要写入模式的锁)。在Reader任务中,使用readLock()方法得到锁(需要读取模式的锁)。最后在OptimisticReader任务中,先使用tryOptimisticRead()方法,然后使用validate()方法判断是否能够访问数据。

如果前两个方法能够控制锁的话,保持等待直到获得锁。tryOptimisticRead()方法始终返回值,如果无法使用锁的话则返回0,否则是非零整数。切记在这种情况下,需要使用validate()方法判断是否确定能够访问数据。

下图显示运行范例输出的部分结果:

pics/02_06.jpg

当Writer任务控制锁时,Reader和OptimisticReader均无法访问值。readLock()方法中断Reader任务,而在OptimisticReader任务中,调用validate()方法返回false并且调用tryOptimisticRead()方法返回0表明其它线程在写入模式下控制锁。当Writer任务释放锁时,Reader和OptimisticReader均能访问共享对象的值。

扩展学习

还需要了解StampedLock类的其它方法:

  • tryReadLock()和tryReadLock(long time , TimeUnit unit):尝试在读取模式中获得锁。如果无法得到,第一个方法立即返回,第二个方法等待参数中的传入的时间值再返回。这些方法同样返回必须被检查的标记值(stamp != 0)。
  • tryWriteLock()和tryWriteLock(long time , TimeUnit unit):尝试在写入模式中获得锁。如果无法得到,第一个方法立即返回,第二个方法等待参数中的传入的时间值再返回。这些方法同样返回必须被检查的标记值(stamp != 0)。
  • isReadLocked()和isWriteLocked():如果锁分别在读取模式和写入模式中正被占用,返回这些方法。
  • tryConvertToReadLock(long stamp)、tryConvertToWriteLock(long stamp)、和tryConvertToOptimisticRead(long stamp):将标记值作为参数传入,尝试转换成方法名中指定的模式。如果能够转换,返回新的标记值,否则返回0。
  • unlock(long stamp:释放锁的对应模式。

更多关注

  • 本章中”锁同步代码块“小节。

猜你喜欢

转载自blog.csdn.net/nicolastsuei/article/details/84503920
今日推荐