从Java 5开始,Java提供了一种功能更强大的线程同步机制一-通过 显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock 允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。某些锁可能允许对共享资源并发访问,如ReadWriteLock (读写锁), Lock、 ReadWriteLock是Java 5提供的两个根接口,并为Lock提供了ReentrantLock (可重入锁)实现类,为ReadWriteLock 提供了ReentrantReadWriteLock实现类。
Java 8新增了新型的StampedLock类,在大多数场景中它可以替代传统ReentrantReadWriteLock.
ReentrantReadWriteLock为读写操作提供了三种锁模式: Writing、 ReadingOptimistic、Reading。
在实现线程安全的控制中,比较常用的是ReentrantLock (可重入锁)。使用该Lock对象可以显式地加锁、释放锁,通常使用ReentrantLock的代码格式如下:
classx{
//定义锁对象
privatefinalReentrantLocklock=newReentrantLock();
//定义需要保证线程安全的方法
publicvoidm(){
//加锁
lock.lock();
try{
//需要保证线程安全的代码
//... method body
//使用finally块来保证释放锁
}finally{
lock.unlock();
}
}
使用ReentrantLock 对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。通过使用ReentrantLock 对象,可以把Account 类改为如下形式,它依然是线程安全的。
publicclassAccount
{
//定义锁对象
privatefinalReentrantLocklock=newReentrantLock();
privateStringaccountNo;
privatedoublebalance;
publicAccount(){}
publicAccount(StringaccountNo,doublebalance)
{
this.accountNo=accountNo;
this.balance=balance;
}
publicvoidsetAccountNo(StringaccountNo)
{
this.accountNo=accountNo;
}
publicStringgetAccountNo()
{
returnthis.accountNo;
}
publicdoublegetBalance()
{
returnthis.balance;
}
publicvoiddraw(doubledrawAmount)
{
lock.lock();
try
{
//账户余额大于取钱数目
if(balance>=drawAmount)
{
//吐出钞票
System.out.println(Thread.currentThread().getName()+
"取钱成功!吐出钞票:"+drawAmount);
try
{
Thread.sleep(1);
}
catch(InterruptedExceptionex)
{
ex.printStackTrace();
}
//修改余额
balance-=drawAmount;
System.out.println("\t余额为: "+balance);
}
else
{
System.out.println(Thread.currentThread().getName()+
"取钱失败!余额不足!");
}
}
finally
{
lock.unlock();
}
}
publicinthashCode()
{
returnaccountNo.hashCode();
}
publicbooleanequals(Objectobj)
{
if(obj!=null&&obj.getClass()==Account.class)
{
Accounttarget=(Account)obj;
returntarget.getAccountNo().equals(accountNo);
}
returnfalse;
}
}
上面程序中的第一行粗体字代码定 义了-个ReentrantLock对象,程序中实现draw()方法时,进入方法开始执行后立即请求对ReentrantLock 对象进行加锁,当执行完draw()方法的取钱逻辑之后,程序使用finally块来确保释放锁。
其他:
同步方法或同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。
虽然同步方法和同步代码块的范围机制使得多线程安全编程非常方便而且还可以避免很多涉及锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。Lock 提供了同步方法和同步代码块所没有的其他功能,包括用于非块结构的tryLock()方法,以及试图获取可中断锁locknterruptibly()方法,还有获取超时失效锁的tryLock(long, TimeUni)方法。
ReentrantLock锁具有可重入性,也就是说,-一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持 一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock(加锁后,必须显式调用unlock(来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。