并发编程的艺术 (学习笔记)-锁

5.1 lock锁

5.2 队列同步器(AbstractQueuedSynchronizer)同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义,可以这样理解二者之间的关系:锁是面向使用者的,他定义了使用者与锁交互的接口,隐藏了实现细节,同步器面向的是锁的实现者,他简化了锁的实现方式,屏蔽了同步状态管理,线程的排队,等待与唤醒等底层操作,锁和同步器很好地隔离了使用者和实现者所需要关注的领域。

5.2.1 队列同步器的接口与示例 

1,自定义同步锁 

package com.souche.sts.study;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
* @description: 自定义独占锁
*
* @author: heqiang
*
* @create: 2019-09-25 10:16
**/
public class Mutex implements Lock {

//静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -1776271486756692747L;

//是否处于占用状态
protected boolean isHeldExclusively() {
return getState() == 1;
}

//当状态为0的时候获取锁
public boolean tryAcquire(int acquire) {
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 ConditionObject();
}
}

private final Sync sync = new Sync();


@Override
public void lock() {
sync.acquire(1);
}

@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}

@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}

@Override
public void unlock() {
sync.release(1);
}

@Override
public Condition newCondition() {
return sync.newCondition();
}
}

2,独占式同步状态获取与释放

/*
* 同步状态获取,节点构造,加入同步队列以及同步队列自旋
*/
public final void acquire(int arg) {
//tryAcquire保证安全的获取同步状态,获取失败,则通过addWaiter加入队列尾部,
//最后调用acquireQueued,死循环获取同步状态
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}


//
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
//确保安全的添加线程
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

//死循环的保证节点的正确添加
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

//进入队列后,自旋获取同步状态,获得就退出自旋
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//只有头节点才能获取同步状态
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

独占式同步状态获取流程,也就是acquire方法:

 

 

释放锁:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒后面等待的线程
unparkSuccessor(h);
return true;
}
return false;
}

独占式总结:

在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会加入到队列中并在队列中进行自旋,移出队列的条件时前驱节点为头节点且成功获取了同步状态,在释放同步状态时,同步器调用tryRelease释放同步状态,然后唤醒头节点的后续节点。

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

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态,

//
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
//如果大于等于表示能够获取状态,所以退出自旋的条件就是大于等于0,
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

//释放锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

不同之处:

tryReleaseShared方法必须确保同步状态线程安全释放,一般是通过循环喝cas来保证,因为释放同步状态的操作会同时来自多个线程。 
4, 独占式超时获取同步状态

//
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}


 

自定义只能2个线程获得的锁:在码云中
5.3 重入锁(ReentrantLock)

synchronized关键字隐式的支持重进入。

5.3.1 实现重进入

重进入是指任意线程再获取到锁之后能够再次获取该锁而不会被锁阻塞。

两个问题:

线程再次获取锁:

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

锁的最终释放:

线程重复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) // 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;
    //当线程最终全部释放完后,状态为0,占有线程设置为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

5.3.2 公平与非公平获取锁公平性与否是针对获取锁而言,

如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是fifo。

//构造函数,根据fair选择公平锁还是非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

//判断获取锁
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //判断加入了同步队列中当前节点是否有前驱节点,这是与nonfairTryAcquire方法不同的地方。
        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;
}

公平锁保证了锁获取按照fifo原则,而代价是进行了大量的线程切换,非公平锁虽然可能造成线程饥饿,但极少线程切换,保证了其更大的吞吐量。

5.4 读写锁(ReentrantReadWriteLock)

前面提到都是排他锁,这些锁同一时刻只允许一个线程进行访问。而读写锁在同一时刻可以允许多个读线程访问,而写线程访问时,其他的读线程和其他线程都被阻塞,读写锁维护了一对锁。

读写锁优点:保证了写操作对读操作的可见性以及并发性的提升,还能简化读写交互场景的编程方式。

 

示例:
/**
 * @description: 读写锁示例
 *
 * @create: 2019-09-26 11:35
 **/
public class Cache {

    static Map<String,Object> map = new HashMap<>();
    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();
        }
    }
}

5.4.2 分析实现

1,读写状态的设计依旧是维护同步器的一个同步状态,读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程状态。

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

 

 2,写锁的获取与释放

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

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    //获取当前写锁状态
    int c = getState();
    //获取当前读锁状态
    int w = exclusiveCount(c);
    if (c != 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;
}

 

释放:
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

 

3,读锁的获得与释放
读锁时一个支持重入的共享锁,可支持多线程同时获取状态并且可以重入,当前线程在获取读锁时,如果写锁已经被其他线程获取,则进入等待状态。
简化版的tryAcquireShared(便于理解):
protected final int tryAcquireShared(int unused) {
    for(;;){
        int c = getState();
        int nextc = c + (1<<16);
        if(nextc < c){
            throw new Err();
        }
        //如果其他线程已经获取写锁,则当前线程获取失败进入等待
        if (exclusiveCount(c) != 0 && owner != Thread.currentThread) {
            return -1;
        }
        if (compareAndSetState(c,nextc)) {
            return 1;
        }
    }
}
实际的tryAcquireShared:
protected final int tryAcquireShared(int unused) {
    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);
}
4,锁降级
定义:写锁降级成为读锁:线程在拥有写锁的时候,再去获取读锁,然后释放写锁的过程
示例:
public void processData() {
volatile boolean update = true;
    r.lock();
    if(!update){
        //必须先释放读锁
        r.unlock();
        //降级先需要获取写锁
        w.lock();
        try {
            if(!update){
                //处理
                update = true;
            }
            r.lock();
        } finally {
            w.unlock();
        }
        //降级完成,
    }
}

5.6 Condition接口

Condition与lock配合可以实现等待/通知模式。

 

示例:
public class ConditionUseCase {
    
    Lock lock  = new ReentrantLock();
    Condition condition = lock.newCondition();

    /**
     * 等待后释放锁
     * @throws InterruptedException
     */
    public void conditionWait() throws InterruptedException {
        lock.lock();
        try {
            condition.await();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 通知当前线程,当前线程才从await返回,返回前已经获取了锁。
     * @throws InterruptedException
     */
    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

有界队列:
public class BoundedQueue<T> {

    private Object[] items;

    private int addIndex,removeIndex,count;

    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();

    public BoundedQueue (int size) {
        items = new Object[size];
    }

    /**
     * 添加一个元素,如果数据满,则添加线程进入等待状态,直到有空位
     * @param t
     * @throws InterruptedException
     */
    public void add(T t) throws InterruptedException {
        lock.lock();

        try {
            while(count == items.length) {
                notFull.await();
            }
            items[addIndex] = t;
            if(++addIndex == items.length){
                addIndex = 0;
            }
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 由头部删除一个元素,如果数组为空,则删除线程进入等待
     * @return
     * @throws InterruptedException
     */
    public T remove() throws InterruptedException {
        lock.lock();
        try {
            while (count  == 0) {
                notEmpty.await();
            }
            Object x = items[removeIndex];
            if(++removeIndex == items.length) {
                removeIndex = 0;
            }
            --count;
            notFull.signal();
            return (T) x;
        } finally {
            lock.unlock();
        }
    }
}

2,Condition的实现

Condition是同步器AbstractQueuedSynchronizer的内部类。每个Condition内部都有一个等待对列

2.1 等待队列 

 

2.2 等待
调用await方法,会使当前线程进入等待队列并释放锁。
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //当前线程进入等待队列
    Node node = addConditionWaiter();
    //释放同步状态,也就是释放锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
3,通知
调用sinal方法,将会唤醒在等待队列中等待时间最长的节点(首节点)
public final void signal() {
    检查当前线程必须是获取了锁的线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值