StampedLock 学习

StampedLock是并发包里面jdk8版本新增的一个锁,
该锁提供了三种模式的读写控制,三种模式分别如下:

      写锁writeLock,是个排它锁或者叫独占锁,同时只有一个线程可以获取该锁,当一个线程获取该锁后,其它请求的线程必须等待,当目前没有线程持有读锁或者写锁的时候才可以获取到该锁,请求该锁成功后会返回一个stamp票据变量用来表示该锁的版本,当释放该锁时候需要unlockWrite并传递参数stamp。
      悲观读锁readLock,是个共享锁,在没有线程获取独占写锁的情况下,同时多个线程可以获取该锁,如果已经有线程持有写锁,其他线程请求获取该读锁会被阻塞。这里讲的悲观其实是参考数据库中的乐观悲观锁的,这里说的悲观是说在具体操作数据前悲观的认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据加锁,这是在读少写多的情况下的一种考虑,请求该锁成功后会返回一个stamp票据变量用来表示该锁的版本,当释放该锁时候需要unlockRead并传递参数stamp。
      乐观读锁tryOptimisticRead,是相对于悲观锁来说的,在操作数据前并没有通过CAS设置锁的状态,如果当前没有线程持有写锁,则简单的返回一个非0的stamp版本信息,获取该stamp后在具体操作数据前还需要调用validate验证下该stamp是否已经不可用,也就是看当调用tryOptimisticRead返回stamp后到到当前时间间是否有其他线程持有了写锁,如果是那么validate会返回0,否者就可以使用该stamp版本的锁对数据进行操作。由于tryOptimisticRead并没有使用CAS设置锁状态所以不需要显示的释放该锁。该锁的一个特点是适用于读多写少的场景,因为获取读锁只是使用与或操作进行检验,不涉及CAS操作,所以效率会高很多,但是同时由于没有使用真正的锁,在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性还是得到保障的。

一个小的案例:

纳税人

package com.test.stampedlock;
/** 
* @author arun.pandey 
*/
public class TaxPayer {
  private String taxPayerName; 
  private String taxPayerSsn; 
  private double taxAmount;  
  public String getTaxPayerName() {  
    return taxPayerName; 
  } 
  public void setTaxPayerName(String taxPayerName) {  
    this.taxPayerName = taxPayerName; 
  } 
  public String getTaxPayerSsn() {  
    return taxPayerSsn; 
  } 
  public void setTaxPayerSsn(String taxPayerSsn) {  
    this.taxPayerSsn = taxPayerSsn; 
  } 
  public double getTaxAmount() {  
    return taxAmount; 
  } 
  public void setTaxAmount(double taxAmount) {  
    this.taxAmount = taxAmount; 
  }
}

国家税收部门

package com.test.stampedlock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author arun.pandey
 */
public class IncomeTaxDept {
	public static final Log LOG = LogFactory.getLog(IncomeTaxDept.class);
	private List<TaxPayer> taxPayersList;
	private double totalRevenue;
	private final StampedLock sl = new StampedLock();

	public IncomeTaxDept(long revenue, int numberOfTaxPayers) {
		this.totalRevenue = revenue;
		taxPayersList = new ArrayList<TaxPayer>(numberOfTaxPayers);
	}

	/**
	 * This method is to show the feature of writeLock() method
	 */
	public void payTax(TaxPayer taxPayer) {
		double taxAmount = taxPayer.getTaxAmount();
		long stamp = sl.writeLock();
		try {
			totalRevenue += taxAmount;
			LOG.info(taxPayer.getTaxPayerName() + " paid tax, now Total Revenue --->>> " + this.totalRevenue);
		} finally {
			sl.unlockWrite(stamp);
		}
	}

	/**
	 * This method is to show the feature of writeLock() method
	 */
	public double getFederalTaxReturn(TaxPayer taxPayer) {
		double incomeTaxRetunAmount = taxPayer.getTaxAmount() * 10 / 100;
		long stamp = sl.writeLock();
		try {
			this.totalRevenue -= incomeTaxRetunAmount;
		} finally {
			sl.unlockWrite(stamp);
		}
		return incomeTaxRetunAmount;
	}

	/**
	 * This method is to show the feature of readLock() method
	 */
	public double getTotalRevenue() {
		long stamp = sl.readLock();
		try {
			return this.totalRevenue;
		} finally {
			sl.unlockRead(stamp);
		}
	}

	/**
	 * This method is to show the feature of tryOptimisticRead() method
	 */
	public double getTotalRevenueOptimisticRead() {
		long stamp = sl.tryOptimisticRead();
		double balance = this.totalRevenue;
		// calling validate(stamp) method to ensure that stamp is valid, if not
		// then acquiring the read lock
		if (!sl.validate(stamp)) {
			LOG.info("stamp for tryOptimisticRead() is not valid now, so acquiring the read lock");
			stamp = sl.readLock();
			try {
				balance = this.totalRevenue;
			} finally {
				sl.unlockRead(stamp);
			}
		}
		return balance;
	}

	/**
	 * This method is to show the feature of tryConvertToWriteLock() method
	 */
	public double getStateTaxReturnUisngConvertToWriteLock(TaxPayer taxPayer) {
		double incomeTaxRetunAmount = taxPayer.getTaxAmount() * 5 / 100;
		long stamp = sl.readLock();
		try {
			// Trying to upgrade the lock from read to write
			 long ws = sl.tryConvertToWriteLock(stamp);
			// Checking if tryConvertToWriteLock got success otherwise call
			// writeLock method
			if (ws == 0L) {
				LOG.info("stamp is zero for tryConvertToWriteLock(), so acquiring the write lock");
				sl.unlockRead(stamp);
				stamp = sl.writeLock();
			}else{
				stamp = ws;
			}
			this.totalRevenue -= incomeTaxRetunAmount;
		} finally {
			sl.unlock(stamp);
		}
		return incomeTaxRetunAmount;
	}

	public void registerTaxPayer(TaxPayer taxPayer) {
		taxPayersList.add(taxPayer);
	}

	public List<TaxPayer> getTaxPayersList() {
		return taxPayersList;
	}
}

测试主方法

package com.test.stampedlock;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author arun.pandey
 */
public class IncomeTaxClient {
	
	public static final Log LOG = LogFactory.getLog(IncomeTaxClient.class);
	private static final int NUMBER_OF_TAX_PAYER = 1000;
	private static IncomeTaxDept incomeTaxDept = new IncomeTaxDept(1000, NUMBER_OF_TAX_PAYER);

	public static void main(String[] args) {
		registerTaxPayers();
		ExecutorService executor = Executors.newFixedThreadPool(30);
		LOG.info("Initial IncomeTax Department's total revenue is ===>> " + incomeTaxDept.getTotalRevenue());
		for (TaxPayer taxPayer : incomeTaxDept.getTaxPayersList())
			executor.submit(() -> {
				try {
					//Thread.sleep(100);
					incomeTaxDept.payTax(taxPayer);
					double revenue = incomeTaxDept.getTotalRevenue();
					LOG.info("IncomeTax Department's total revenue after tax paid at client code is --->> " + revenue);
					
					double returnAmount = incomeTaxDept.getFederalTaxReturn(taxPayer);
					LOG.info(taxPayer.getTaxPayerName() + " received the Federal return amount = " + returnAmount);
					
					revenue = incomeTaxDept.getTotalRevenueOptimisticRead();
					LOG.info("IncomeTax Department's total revenue after getting Federal tax return at client code is --->> "
									+ revenue);
					
					double stateReturnAmount = incomeTaxDept.getStateTaxReturnUisngConvertToWriteLock(taxPayer);
					LOG.info(taxPayer.getTaxPayerName() + " received the State tax return amount = "
							+ stateReturnAmount);
					
					revenue = incomeTaxDept.getTotalRevenueOptimisticRead();
					LOG.info(
							"IncomeTax Department's total revenue after getting State tax return at client code is --->> "
									+ revenue);
					
					//Thread.sleep(100);
				} catch (Exception e) {
				}
			});
		executor.shutdown();
	}

	private static void registerTaxPayers() {
		for (int i = 1; i < NUMBER_OF_TAX_PAYER + 1; i++) {
			TaxPayer taxPayer = new TaxPayer();
			taxPayer.setTaxAmount(2000);
			taxPayer.setTaxPayerName("Payer-" + i);
			taxPayer.setTaxPayerSsn("xx-xxx-xxxx" + i);
			incomeTaxDept.registerTaxPayer(taxPayer);
		}
	}
}

Java 8:StampedLock,ReadWriteLock以及synchronized的比较