Java中的锁----重入锁

ReentrantLock

重入锁(ReentrantLock):支持重进入的锁,他表示该锁可以支持一个线程对资源的重复加锁。在调用lock()方法时,已经获取到锁的线程,可以再次调用lock()方法获取锁而不被阻塞。 公平锁:在绝对时间上,先对锁进行获取的请求必定先被知足,也就是等待时间最长的线程最优先获取锁。反之则是不公平锁。java

1.实现重进入

  1. 线程再次获取锁。锁须要去识别获取锁的线程是否为当前占据锁的线程,若是是,则再次成功获取。缓存

  2. 锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其余线程可以获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示因此经成功释放。安全

/**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
            非公平性锁获取的示例

         */
        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) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

该方法增长了再次获取同步状态的处理逻辑:经过判断当前线程是否为获取锁的线程来决定获取操做是否成功,若是是获取锁的线程再次请求,则将同步状态值进行增长并返回true,表示获取同步状态成功。 成功获取锁的线程再次获取锁,只是增长了同步状态值,这也就要求ReentrantLock在释放同步状态时减小同步状态值。多线程

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

公平与非公平获取锁的区别

/**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    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;
        }

tryAcquire 是公平锁的获取,同 nonfairTryAcquire 相比,多了判断条件:hasQueuedPredecessors();既加入了同步队列中当前节点是否有前驱节点的判断,若是该方法返回true,则表示有线程比当前线程更早地请求获取锁,所以须要等待前驱线程获取并释放以后才能继续获取锁。并发

读写锁

读写锁:在同一时刻能够容许多个多线程访问,可是在写线程访问时,全部的读线程和其余写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,经过分离读锁和写锁,使得并发性相比通常的排他锁有了很大的提示。app

Java并发包提供读写锁的实现是ReentrantReadWriteLock();特性:公平性选择,重进入,锁降级。less

读写锁的接口与示例

ReadWriteLock 仅定义了 获取读锁和写锁的两个方法,既 readLock()方法 和 WriteLock() 方法,而其实现---ReentranReadWriteLock,除了实现接口方法,还提供了一些便于外界监控其内部状态的方法。oop

package com.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created by cxx on 2018/1/17.
 * 缓存示例说明读写锁的实现方式
 */
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();
        try {
            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();
        }
    }
    
}

Cache组合一个非线程安全的HashMap做为缓存的实现,同时使用读写锁的读锁和写锁来保证Cache是线程安全的。在操做get() 方法中,须要获取读锁,这使得并发访问该方法时不会被阻塞。写操做put 方法和clear方法,在更新HashMap是必须提早获取写锁,当获取写锁后,其余线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放以后,其余读写操做才能继续。post

读写锁的实现方式

ReentrantReadWriteLock 的实现,主要包括:读写状态的设计,写锁的获取与释放、读锁的获取与释放以及锁降级。ui

读写状态的设计

读写锁一样依赖自定义同步器实现同步功能,而读写状态就是其同步器的同步状态。同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器须要在同步状态上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。

若是在一个整型变量上维护多种状态,须要”按位切割使用“这个变量。高16位表示读,低16位表示写。

结论:同步状态值S,S不等于0时,当写状态(S & 0x0000FFFF) 等于 0 时,则读状态(s >> 16) 大于 0,既读锁已被获取。

写锁的获取与释放

写锁是一个支持重进入的排他锁。若是当前线程已经获取了写锁,则增长写状态。若是当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。

protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

除了重入条件的判断以外,增长了一个读锁是否存在的判断。若是存在读锁,则写锁不能被获取。只有等待其余读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其余读写线程的后续访问均被阻塞。

读锁的获取与释放

读锁是一个支持重进入的共享锁,他可以被多个线程同时获取,在没有其余写线程访问(或者写状态为0)时,读锁总会被成功地获取,而所作的也只是(线程安全的)增长读状态。读状态时全部线程获取读锁次数的总和,而每一个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护,这使获取读锁的实现变得复杂。

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

锁降级

锁降级指的是写锁降级成为读锁。若是当前线程用于写锁,而后将其释放,最后在获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住写锁,在获取到读锁,随后释放写锁的过程。

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();
        }
    }

锁降级的必要性:主要是为了保证数据的可见性,若是当前线程不获取读锁而是直接释放写锁,假设此刻另外一个线程获取了写锁并修改了数据,那么当前线程没法感知线程T的数据更新。若是当前线程获取读锁,既遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁以后,线程T才能获取写锁进行数据更新。

RentrantReadWriteLock不支持所升级,目的也是保证数据可见性,若是读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其余获取到读锁的线程是不可见的。

相关文章
相关标签/搜索