乐趣区

关于java:阿里Java并发编程面试题解显式锁Explicit-Locks

点赞再看,养成好习惯!!!

Java5 之前只能用 synchronized 和 volatile,Java5 后 Doug Lea 提供了 ReentrantLock,并非为了代替内置锁,而是当内置锁的机制不实用时,作为一种可抉择的高级性能。

内置锁不实用的场景包含:

  • 无奈中断一个正在期待获取锁的线程
  • 有限的锁期待
  • 内置锁必须放在代码块里(编程有些局限性)

所以提供了 J.U.C 的 Lock 接口及实现。

1. Lock 和 ReentrantLock

之所以叫 ReentrantLock,可了解为两局部

  • Re-entrant
    可重入,lock 多少次都没关系,只须要 unlock 即可,或者 lock 外面嵌套了别的 lock 都能够
  • Lock
    提供了和 synchronized 一样的互斥性和内存可见性,与 synchronized 的 monitor 内存语义一样

2 Synchronized(S) V.S Lock(L)

  • L 是接口,S 是关键字
  • S 异样时,会主动开释线程占有的锁,不会产生死锁
    L 异样时,若没有被动通过 unlock()开释锁,则很有可能造成死锁。所以用 lock 时要在 finally 中开释锁.。
  • L 能够当期待锁的线程响应中断
    应用 S 时,期待的线程将会始终等上来,不能响应中断
  • 通过 L 能够晓得是否胜利取得锁,S 不能够
  • L 能够进步多个线程进行读写操作的效率

3 Lock 的个性

  • 可定时锁期待
  • 可轮询锁期待
  • 可中断锁期待
  • 公平性
  • 实现非块构造的加锁
  • 绑定多个 Condition。通过屡次 newCondition 能够取得多个 Condition 对象, 能够简略的实现比较复杂的线程同步的性能. 通过 await(),signal();

3.1 轮询锁和定时锁

内置锁的死锁问题只能通过重启解决,可定时、可轮询锁提供了另一种抉择:
通过 tryLock 解决

public class DeadlockAvoidance {private static Random rnd = new Random();

    public boolean transferMoney(Account fromAcct,
                                 Account toAcct,
                                 DollarAmount amount,
                                 long timeout,
                                 TimeUnit unit)
            throws InsufficientFundsException, InterruptedException {long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
        long randMod = getRandomDelayModulusNanos(timeout, unit);
        long stopTime = System.nanoTime() + unit.toNanos(timeout); // 定时,轮询

        while (true) {if (fromAcct.lock.tryLock()) {
                try {if (toAcct.lock.tryLock()) {
                        try {if (fromAcct.getBalance().compareTo(amount) < 0)
                                throw new InsufficientFundsException();
                            else {fromAcct.debit(amount);
                                toAcct.credit(amount);
                                return true;
                            }
                        } finally {toAcct.lock.unlock();
                        }
                    }
                } finally {fromAcct.lock.unlock();
                }
            }
            if (System.nanoTime() < stopTime)
                return false;
            NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
        }
    }

    private static final int DELAY_FIXED = 1;
    private static final int DELAY_RANDOM = 2;

    static long getFixedDelayComponentNanos(long timeout, TimeUnit unit) {return DELAY_FIXED;}

    static long getRandomDelayModulusNanos(long timeout, TimeUnit unit) {return DELAY_RANDOM;}

    static class DollarAmount implements Comparable<DollarAmount> {public int compareTo(DollarAmount other) {return 0;}

        DollarAmount(int dollars) {}}

    class Account {
        public Lock lock;

        void debit(DollarAmount d) { }

        void credit(DollarAmount d) { }

        DollarAmount getBalance() {return null;}
    }

    class InsufficientFundsException extends Exception {}}

3.2 带有工夫限度的锁

3.3 可中断的锁

3.4 对于 Condition

最典型的就是阻塞的有界队列的实现。

public class BoundedBuffer {private static final Logger logger = LoggerFactory.getLogger(BoundedBuffer.class);

    final Lock lock = new ReentrantLock();

    final Condition notFull = lock.newCondition();

    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[2]; // 阻塞队列

    int putptr, takeptr, count;

    private void log(String info) {logger.info(Thread.currentThread().getName() + "-" + info);
    }

    public void put(Object x) throws InterruptedException {log(x + ", 执行 put");
        lock.lock();
        log(x + ",put lock.lock()");
        try {while (count == items.length) { // 如果队列满了,notFull 就始终期待
                log(x + ",put notFull.await() 队列满了");
                notFull.await(); // 调用 await 的意思取反,及 not notFull -> Full}
            items[putptr] = x; // 终于能够插入队列
            if (++putptr == items.length) {putptr = 0; // 如果下标达到数组边界,循环下标置为 0}
            ++count;
            log(x + ",put 胜利 notEmpty.signal() 周知队列不为空了");
            notEmpty.signal(); // 唤醒 notEmpty} finally {log(x + ",put lock.unlock()");
            lock.unlock();}
    }

    public Object take() throws InterruptedException {log("执行 take");
        lock.lock();
        Object x = null;
        log("take lock.lock()");
        try {while (count == 0) {log("take notEmpty.await() 队列为空等等");
                notEmpty.await();}
            x = items[takeptr];
            if (++takeptr == items.length) {takeptr = 0;}
            --count;
            log(x + ",take 胜利 notFull.signal() 周知队列有残余空间了");
            notFull.signal();
            return x;
        } finally {lock.unlock();
            log(x + ",take lock.unlock()");
        }
    }

    public static void main(String[] args) throws InterruptedException {final BoundedBuffer bb = new BoundedBuffer();
        ExecutorService executor = Executors.newFixedThreadPool(10);

        for (char i = 'A'; i < 'F'; i++) {
            final char t = i;
            executor.execute(() -> {
                try {bb.put(t);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            });
        }

        List<Character> res = new LinkedList<>();
        for (char i = 'A'; i < 'F'; i++) {executor.execute(() -> {
                try {char c = (char) bb.take();
                    res.add(c);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            });
        }

        try {executor.awaitTermination(2, TimeUnit.SECONDS);
        } catch (InterruptedException ie) {ie.printStackTrace();
        }

        logger.info(res.toString());
        executor.shutdownNow();}
}

4 性能思考因素

Java5 的时候 J.U.C 的 ReentrantLock 锁竞争性能十分好,到了 Java6 应用了改良后的算法来治理内置锁,所以当初差不太多了,只好一点点

竞争性能的影响可伸缩性的要害因素:如果有越多的资源被消耗在锁的治理和线程调度上,那么应用程序失去的资源就越少,锁的实现形式越好,将须要越少的零碎调用和上下文切换。

5 公平性

ReentrantLock 默认创立非偏心的锁,非偏心指被阻塞挂起的线程 (LockSupport.park) 都在 AQS 的 CLH 队列中排队期待本人被唤醒。他们是依照收回的申请程序来排队的,但一旦有一个唤醒的就会和新来的线程竞争锁,新来的可能会“插队”。若新来的胜利获取锁,那么它将跳过所有期待线程而开始执行,这意味着本该被唤醒的线程失败了,对不起您回到队列的尾部持续等。

个别,非偏心锁的性能要好于偏心锁。因为一个线程被唤醒是须要工夫的,挂起线程和唤醒复原线程都存在开销,这个空隙如果有其余线程处于 ready 状态,无需上下文切换,那么间接运行就行。

A 持有锁,B 申请,但 B 在复原的过程中,C 能够插队 ” 非偏心 ” 的获取锁,而后执行再开释,这时候 B 刚刚好做完上下文切换能够执行,这个对于 B 和 C 来说是一个“双赢”的场面,是进步吞吐量的起因。

JVM 也没有在其内置锁上采纳公平性的机制。

6 选型

除非应用到 3 提到的高级个性,或者内置锁无奈满足需要时,否则还是诚实用内置锁,毕竟是 JVM 本身提供的,而不是靠类库,因而可能会执行一些优化。

另外内置锁在利用 kill -3 dump thread 的时候能够发现栈帧上的一些 monitor lock 的信息,辨认死锁,而 J.U.C 的锁这方面就不太行,当然 JAVA6 之后提供了治理和调试接口解决了。

7 读 - 写锁

ReentrantLock 每次只有一个线程能持有锁,然而这种严格的互斥也会克制并发。会克制

  • 写 / 写
  • 写 / 读
  • 读 / 读

抵触,然而很多状况下读操作是十分多的,如果放宽加锁的需要,容许多个读操作能够同时拜访数据,那么就能够晋升性能。然而要保障读取的数据是最新的, 不会有其余线程批改数据。

应用 ReadWriteLock 的场景:

  • 一个资源能够被多个读操作拜访
  • 被一个写操作拜访
  • 但二者不能同时进行

如果读线程正在持有锁,这时候另外一个写线程,那么会优先获取写锁:

public class ReadWriteMap<K, V> {
    private final Map<K, V> map;
    private final ReadWriteLock lock=new ReentrantReadWriteLock();
    private final Lock r=lock.readLock();
    private final Lock w=lock.writeLock();

    public ReadWriteMap(Map<K, V> map) {this.map=map;}

    public V put(K key, V value) {w.lock();
        try {return map.put( key, value);
        } finally {w.unlock();
        }
    }

    public V remove(Object key) {w.lock();
        try {return map.remove( key);
        } finally {w.unlock();
        }
    }

    public void putAll(Map<? extends K, ? extends V> m) {w.lock();
        try {map.putAll( m);
        } finally {w.unlock();
        }
    }

    public void clear() {w.lock();
        try {map.clear();
        } finally {w.unlock();
        }
    }

    public V get(Object key) {r.lock();
        try {return map.get( key);
        } finally {r.unlock();
        }
    }

    public int size() {r.lock();
        try {return map.size();
        } finally {r.unlock();
        }
    }

    public boolean isEmpty() {r.lock();
        try {return map.isEmpty();
        } finally {r.unlock();
        }
    }

    public boolean containsKey(Object key) {r.lock();
        try {return map.containsKey( key);
        } finally {r.unlock();
        }
    }

    public boolean containsValue(Object value) {r.lock();
        try {return map.containsValue( value);
        } finally {r.unlock();
        }
    }
}
退出移动版