《Java并发编程的艺术》--Java中的锁

Posted 嘉禾世兴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java并发编程的艺术》--Java中的锁相关的知识,希望对你有一定的参考价值。

No1:

Lock接口

Lock lock = new ReentrantLock();
lock.lock();
try{
}finally{
    lock.unlock();
}

No2:

不要讲获取锁的过程写在try块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放

No3:

No4:

队列同步器(同步器)是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者期望它能够成为实现大部分同步需求的基础。

主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

No5:

锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节

同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。

锁和同步器很好地隔离了使用者和实现者所需关注的领域。

No6:

同步器提供的模板方法基本上分为3类:

1)独占式获取与释放同步状态

2)共享式获取与释放同步状态

3)查询同步队列中的等待线程情况

No7:

独占锁:在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁。

class Mutex implements Lock{
    //静态内部类,自定义同步器
    private static class Sync extends AbstractQueueSynchronizer{
        //是否处于占用状态
        protected boolean isHeldExclusively(){
            return getState() == 1;
        }
        //当状态为0的时候获取锁
        public boolean tryAcquire(int acquires){
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //释放锁,将状态设置为0
        protected boolean tryRelease(int release){
            if(getState() == 0)throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        //返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition(){
            return new Condition();
        }
    }
    //仅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();
    public void lock(){sync.acquire(1)};
    public boolean tryLock(){
        return sync.tryAcquire(1);
    }
    public void unlock(){
        sync.release(1);
    }
    public Condition newCondition(){
        return sync.newCondition();
    }
    public boolean isLocked(){
        return sync.isHeldExclusively();
    }
    public boolean hasQueuedTreads(){
        return sync.hasQueuedTreads();
    }
    public void lockInterruptibly() throws InterruptedException{
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException{
        return sync.tryAcquireNanos(1,unit.toNanos(timeout));
    }
}

No8:

重入锁ReentrantLock,就是支持重进入得锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择

No9:

重进入时指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决以下两个问题

1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取

2)锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放

final boolean nonfairTryAcquire(int acquires){
    final Thread current = Thread.currentThread();
    int c = getState();
    if(c == 0){
        if(compareAndSetState(0,acquires)){
            setExclusiveOwnerThread(current);
            return true;
        }
    }else if(current == getExclusiveOwnerThread()){
        int nextc = c + acquires;
        if(nextc <0){
            throw new Error("Maximum lock count exceeded");
        }
        setState(nextc);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int release){
    int c = getState() - release;
    if(Thread.currentThread()!=getExclusiveOwnerThread()){
        throw new IllegalMonitorStateException();
    }
    boolean free = false;
    if(c == 0){
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

 No10:

独占锁和重入锁都是排他锁,这些锁在同一时刻只允许一个线程进行访问。

而读写锁ReentrantReadWriteLock在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他线程均被阻塞。

读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升

public class Cache{
    static Map<String,Object> map = new HashMap<String,Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    //获取一个key对应的value
    public static final Object get(String key){
        r.lock();
        tyr{
            return map.get(key);
        }finally{
            r.unlock();
        }
    }
    //设置key对应的value,并返回旧的value
    public static final Object put(String key,Object value){
        w.lock();
        try{
            return map.put(key,value);
        }finally{
            w.unlock();
        }
    }
    //清空所有的内容
    public static final void clear(){
        w.lock();
        try{
            map.clear();
        }finally{
            w.unlock();
        }
    }
}

 当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。

No11:

读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问时,读锁总会被成功的获取,而所做的也只是(线程安全的)增加读状态。

 No12:

锁降级是指写锁降级成为读锁。是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

public void processData(){
    readLock.lock();
    if(!update){
        //必须先释放读锁
        readLock.unlock();
        //锁降级从写锁获取到开始
        writeLock.lock();
        try{
            if(!update){
                //准备数据的流程(略)
                update = true;
            }
            readLock.lock();
        }finally{
            writeLock.unlock();
        }
        //锁降级完成,写锁降级为读锁
    }
    try{
        //使用数据的流程(略)
    }finally{
        readLock.unlock();
    }
}

No12:

RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。

No13:

No14:

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,COndition是依赖Lock对象的

Lock lock = new ReentranLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException{
    lock.lock();
    try{
        condition.await();
    }finally{
        lock.unlock();
    }
}
public void conditionSignal() throws InterruptedException{
    lock.lock();
    try{
        condition.signal();
    }finally{
        lock.unlock();
    }
}

 

No15:

ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列,该队列是Condition对象实现等待/通知功能的关键。

以上是关于《Java并发编程的艺术》--Java中的锁的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程的艺术(5-10)学习总结

Java并发编程艺术系列-一并发编程问题与解决

并发编程的挑战(Java并发编程的艺术)

《java并发编程的艺术》学习小结

《java并发编程的艺术》学习小结

《Java并发编程的艺术》读书笔记:等待/通知机制