java并发编程之二

来源:互联网 发布:vb6.0 数据库编程实例 编辑:程序博客网 时间:2024/05/20 00:50

重入锁ReentrantLock

支持同一线程重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此以外,该锁还支持获取

锁时的公平和非公平性选择。不支持重进入的锁在实现tryAcquire()方法时没有考虑占有锁的线程再次获

取锁的场景,而在调用tryAcquire()方法时返回false,导致该线程被阻塞。而synchronized关键字是支持重

进入的锁,比如一个synchronized修饰的递归方法,能连续多次获得锁。

实现重进入

1.线程再次获取锁。锁需要去判断获取该锁的线程是否为当前持有锁的线程,如果是,则再次成功获得锁

2.锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁.锁的最终释

放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等

于0时表示锁已经被成功释放。

公平性获取锁

/**         * 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() &&//hasQueuedPredecessors保证了公平获取锁                    compareAndSetState(0, acquires)) {//此处不是重入,需要CAS操作去获取锁                    setExclusiveOwnerThread(current);                    return true;                }            }            else if (current == getExclusiveOwnerThread()) {                int nextc = c + acquires;//重入时自增,一般acquire==1                if (nextc < 0)                    throw new Error("Maximum lock count exceeded");                setState(nextc);//重入的话,本来该线程已占有锁,是有同步保证的,所以此处不用CAS                return true;            }            return false;        }
hasQueuedPredecessors()方法用于判断同步队列中是否有其他线程比该线程等待更久。如果没在

tryAcquire()这个可重写方法中加入这个判断,由模板方法AbstractQueuedSynchronizer#acquire()

可知,那样就是不公平获取锁。

不公平获取锁

protected final boolean tryAcquire(int acquires) {            return nonfairTryAcquire(acquires);        }
/**         * 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;        }

释放锁(获取时涉及到公平性,释放是和公平性无关的)

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);//因为虽是被获取锁多次,但是这个释放动作不用加上CAS操作,因还是保证了这是单线程操作            return free;        }

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

公平性与否是针对获取锁而言的,释放锁是没有公平性一说的。如果一个锁是公平的,那么锁的获取顺序就应该

符合请求的绝对顺序,也就是FIFO

公平的锁机制往往没有非公平的效率高,但是公平锁能够减少“饥饿”发生的概率。效率高的原因在于,当一个线程

A去获取锁时,不用先插到同步队列尾部,然后放弃当前时间片,而虽然队列头线程B释放锁时唤醒了其后继线程C,

只是意味着线程C从阻塞状态变为就绪状态,并没有马上获得cpu执行权。那么平均等待时间就长了,本来A可以马

上获得锁,那么资源可以更充分的得到利用。如果公平获取,资源就空闲了,还要等C获取cpu才能得到利用。还有一

种类似情况,就是一个线程D释放锁后,又要获取锁。公平获取时,意味着D要放弃cpu,等那个被唤醒的线程但没有

执行权的线程E,此时锁又空着没利用好了,如果是非公平获取,那么情况会是D因有执行权,释放后,很容易会再次

获取到锁,那么D可能就会很快完成任务,有限资源(锁)就可以得到更连续充分的利用,也不用那么频繁的切换线程。

总而言之,就是相同的吞吐量(获取锁的次数),非公平性获取锁的方式耗时少很多。

读写锁

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

维护了一对锁,一个读锁一个写锁。

ReadWriteLock的接口只有以下两个

Lock readLock( ) 返回一个读锁。

Lock writeLock( ) 返回一个写锁,和读锁操作的是同一个int变量,使得同步变得简单,只需同步一个状态变量

ReentrantReadWriteLock implements ReadWriteLock 增加的一些便于外界监控其内部工作状态的方法

* int getReadLockCount() 返回当前读锁被获取的次数。重入时也记入其中,所以并不等于获取读锁的线程数

* int getReadHoldCount() 返回当前获取读锁的次数。使用ThreadLocal来保存当前线程获取的次数。

* boolean isWriteLocked() 判断写锁是否被获取

* int getWriteHoldCount() 返回当前写锁被获取的次数

读写锁的实现

在一个同步状态上维护多个读线程和一个写线程的状态,这个同步状态(就是一个int变量)的设计是读写锁实现

的关键。如果在一个整型变量上维护多种状态(写线程之间的同步状态和读和写的同步状态),就需要“按位切割使

用”这个变量,读写锁将变量切分成了两部分,高16位表示读,低16位表示写。

那么(S>>>16)表示读锁状态,(S&0x0000FFFF)表示写锁状态。

当写锁状态为0且读锁大于0时,表示读锁已被获取;当写锁大于0时,表示写锁已被获取。写锁是一个支持重入的排

他锁。

ReentrantReadWriteLock中获取写锁,因为是独占式,用tryAcquire()方法

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);//如果是重入,则获取锁不用CAS操作保证同步                return true;            }            if (writerShouldBlock() ||//这里判断读锁是否已被获取,是则返回false,这里说明不允许获取读锁后没释放就获取写锁                !compareAndSetState(c, c + acquires))//不是重入的情况下,获取锁                return false;            setExclusiveOwnerThread(current);            return true;        }
exclusiveCount()方法是用于查询写锁状态,即低16位

ReentrantReadWriteLock中获取读锁,因为是读线程间是共享式,使用tryAcquireShared()

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 != current.getId())                        cachedHoldCounter = rh = readHolds.get();                    else if (rh.count == 0)                        readHolds.set(rh);                    rh.count++;                }                return 1;            }            return fullTryAcquireShared(current);        }
锁降级
锁降级指的是写锁降级成读锁(注意反过来是不成立的)。如果当前线程拥有写锁,然后将其释放,最后再获取读

锁,这种分段完成的过程不能称之为锁降级。锁降级是指线程占有写锁,再获取读锁,随后释放写锁的过程。

这样线程A获得写锁,写完数据后,马上安全的读到数据,而且还可以让其他读线程也一起读到数据。假设释放了

写锁,再去获取读锁,那么就可能读不到线程A写的数据了,因为可能让线程B获取了写锁,然后A就阻塞了,后面

读到的将是B写的数据。

由获取写锁的代码可知,ReentrantReadWriteLock是不支持读锁被占有的情况下获得读锁的,级不支持锁升级(

占有读锁->获取写锁->释放读锁的过程)。







的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的


原创粉丝点击