【Java并发】显式锁

2018-03-25  本文已影响18人  Rockie_h

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();
}
上一篇下一篇

猜你喜欢

热点阅读