当前位置 博文首页 > JavaEdge全是干货的技术号:【大厂Java并发编程面试题解】显式锁

    JavaEdge全是干货的技术号:【大厂Java并发编程面试题解】显式锁

    作者:[db:作者] 时间:2021-07-21 09:42

    Java5之前只能用synchronized和volatile,Java5后Doug Lea提供了ReentrantLock,并非为了替代内置锁,而是当内置锁的机制不适用时,作为一种可选择的高级功能。
    内置锁不适用的场景包括:

    1. 无法中断一个正在等待获取锁的线程
    2. 无限的锁等待
    3. 内置锁必须放在代码块里(编程有些局限性)

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

    1. Lock和ReentrantLock

    Lock接口定义
    使用范例
    之所以叫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();
                    }
                })