【Java并发】显式锁
Lock与ReentrantLock
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性,在获取ReentrantLock时,有着与同步代码块相同的内存语义,在释放ReentrantLock时,有着与退出同步代码块相同的内存语义。此外,与synchronized一样,ReentrantLock提供了可重入的加锁语义。
两者比较:
1 显式锁提供定时的锁等待、可中断的锁等待、公平性,以及非块结构的加锁。性能提升;
2 内置锁更简洁,使用风险更低,显式锁需要显式的调用方法释放锁。
3 未来性能可能持续提升的是内置锁,因为内置锁是JVM的内置属性,它能执行一些优化。
4 Lock无法像内置锁那样自动释放,需要显式释放锁,增加了开发应用的风险。
public interface Lock
{
//获取锁
void lock();
//如果当前线程未被中断,则获取锁
void lockInterruptibly() throws
InterruptedException;
//如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
boolean tryLock();
//如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException;
//释放锁
void unlock();
//返回绑定到此Lock实例的新Condition实例
Condition newCondition();
}
示例一:使用ReentantLock保护对象状态的典型用法:
Lock lock = new ReentrantLock();
...
lock.lock();
try
{
//更新对象状态
//捕获异常,并在必要时恢复不变性条件
}
finally
{
//必须在finally中显式的释放锁
lock.unlock();
}
示例二:通过tryLock来避免顺序死锁:
private void transfer(Account fromAccount, Account toAccount, int transferAmount, long deadTime)
{
long starttime = System.currentTimeMillis();
while(true)
{
//尝试获取锁,如果失败,则循环尝试,直到获取锁或者超时
if(fromAccount.getLock().tryLock())
{
try
{
if(toAccount.getLock().tryLock())
{
try
{
fromAccount.withDraw(transferAmount);
toAccount.deposit(transferAmount);
count++;
//System.out.println("run count = " + count);
}
finally
{
fromAccount.getLock().unlock();
}
}
}
finally
{
fromAccount.getLock().unlock();
}
}
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
};
if((System.currentTimeMillis() - starttime) < deadTime)
{
return;
}
}
}
定时锁与轮询锁的锁获取模式是由tryLock实现的。与内置锁相比,使用tryLock如果不能获取需要的锁,可以使用可定时的或可轮询的锁获取方式,从而重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁(或者至少会记录失败日志,或采取其他措施)。
示例三:可中断的锁获取方式lockInterruptibly:
//todo
lockInterruptibly方法能够在获得锁的同时保持对中断的响应。
仅当内置锁不能满足要求时,才应该考虑使用ReentrantLock的一些高级功能,包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized。
公平锁
ReentrantLock构造函数提供了两种公平性选择:非公平锁(默认)或者公平锁。
公平锁:线程将按照它们发出请求的顺序来获得锁;
非公平锁:允许“插队”,如果在发出请求的同时,该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。只有当锁被某个线程持有时,新发出请求的线程才会被放入队列;
大多数情况下,非公平锁的性能高于公平锁:
1 公平锁由于在挂起线程和恢复线程时的开销而极大的降低性能;
2 恢复一个挂起的线程与该线程真正开始运行之间存在严重的延迟;
当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。
读写锁
ReentrantLock的互斥规则过于强硬。可通过读写锁进行优化。
一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。
接口:ReadWriteLock
实现类:ReentrantReadWriteLock
public interface ReadWriteLock
{
Lock readLock();
Lock writeLock();
}