Java并发 - Lock接口

作者: 右耳菌 | 来源:发表于2022-06-18 22:10 被阅读0次

    Locks包 类层次结构

    Locks包 类层次结构

    Lock接口

    方法签名 描述 说明
    void lock(); 获取锁(不死不休) 一直获取锁,直到拿到为止
    boolean tryLock(); 获取锁(浅尝辄止) 尝试获得锁,获取不到就算了
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 获取锁(过时不候) 超时限制,超过时间就放弃
    void lockInterruptibly() throws InterruptedException; 获取锁(任人摆布) 可以在外部通过方法中断
    void unlock(); 释放锁
    Condition newCondition();

    结论:
    1、lock()最常用;
    2、lockInterruptibly()方法一般更昂贵,有的impl可能没有实现lockInterruptibly(),只有真的需要效应中断时,才使用,使用之前看看impl对该方法的描述。

    • trylock
    package lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class GetLock_Demo {
        static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
            lock.lock(); //主线程拿到锁
    
            Thread th = new Thread(new Runnable() {
                @Override
                public void run() {
                    boolean rs = lock.tryLock();
                    System.out.println("是否获取到锁: " + rs);
                }
            });
            th.start();
    
            Thread.sleep(2000L);
            th.interrupt();//中断线程运行
            System.out.println("th 线程被中断了");
        }
    }
    
    是否获取到锁: false
    th 线程被中断了
    
    • trylock带超时
    package lock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class GetLock_Demo {
        static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
            lock.lock(); //主线程拿到锁
    
            Thread th = new Thread(new Runnable() {
                @Override
                public void run() {
                    boolean rs = false;
                    try {
                        rs = lock.tryLock(1, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("是否获取到锁: " + rs);
                }
            });
            th.start();
    
            Thread.sleep(2000L);
            th.interrupt();//中断线程运行
            System.out.println("th 线程被中断了");
        }
    }
    
    是否获取到锁: false
    th 线程被中断了
    
    • lockInterruptibly
    package lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class GetLock_Demo {
        static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
            lock.lock(); //主线程拿到锁
    
            Thread th = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lock.lockInterruptibly();
                    } catch (InterruptedException e) {
                        System.out.println("获取锁时被中断了");
                        e.printStackTrace();
                    }
                }
            });
            th.start();
    
            Thread.sleep(2000L);
            th.interrupt();//中断线程运行
            System.out.println("th 线程被中断了");
        }
    }
    
    th 线程被中断了
    获取锁时被中断了
    java.lang.InterruptedException
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        at lock.GetLock_Demo$1.run(GetLock_Demo.java:16)
        at java.lang.Thread.run(Thread.java:748)
    
    • lock and unlock
    package lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class GetLock_Demo {
        static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
            lock.lock(); //主线程拿到锁
    
            Thread th = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("尝试获得锁");
                    lock.lock();
                    System.out.println("获得锁了");
                }
            });
            th.start();
    
            Thread.sleep(2000L);
            th.interrupt();//中断线程运行
            System.out.println("th 线程被中断了");
    
            Thread.sleep(5000L);
            lock.unlock();
        }
    }
    
    尝试获得锁
    th 线程被中断了
    获得锁了
    

    Condition

    Condition 一般是将其中的await和signal成对使用的,且一般是await在前signal在后,而且调用的使用,应该确保本身是获取到锁的情况下,不然会出现以下问题:

    1. await 和 signal 方法应该在lock内部调用,否则会发生 IllegalMonitorStateException 异常

    package lock;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Condition_Demo {
        private static Lock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread() {
                @Override
                public void run() {
    
                    lock.lock();
    
                    try {
                        System.out.println("当前线程:" + Thread.currentThread().getName() + "获得锁");
                        condition.await(); //因为这里将线程挂起,所以后面无法执行
                        System.out.println("当前线程:" + Thread.currentThread().getName() + "开始执行~");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            };
    
            thread.start();
            Thread.sleep(2000L);
            System.out.println("休眠2s,来控制线程");
            condition.signal(); //直接唤醒会报错,因为lock方法执行在Thread-0线程内部,而我们代码在这里执行的是main线程,所以会报错,
        }
    }
    
    当前线程:Thread-0获得锁
    休眠2s,来控制线程
    Exception in thread "main" java.lang.IllegalMonitorStateException
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
        at lock.Condition_Demo.main(Condition_Demo.java:33)
    

    2. signal应该在await后调用,否则会导致死锁

    package lock;
    
    import sync.ReentrantLockDemo;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Condition_Demo {
        private static Lock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000L);
                        System.out.println("休眠3秒,等待主线程先执行.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    lock.lock();
    
                    try {
                        System.out.println("当前线程:" + Thread.currentThread().getName() + "获得锁");
                        condition.await(); //因为这里将线程挂起,所以后面无法执行
                        System.out.println("当前线程:" + Thread.currentThread().getName() + "开始执行~");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            };
    
            thread.start();
            Thread.sleep(2000L);
            System.out.println("休眠2s,来控制线程");
            lock.lock();
            condition.signal(); //直接唤醒会报错,因为lock方法执行在Thread-0线程内部,而我们代码在这里执行的是main线程,所以会报错,
            lock.unlock(); //获取到了这把锁,然后解锁.
            //2.当然这里会出现死锁的,如果signal方法在我们的await之前执行,那么这里就会死锁
        }
    }
    
    休眠2s,来控制线程
    休眠3秒,等待主线程先执行.
    当前线程:Thread-0获得锁
    // 这里死锁了
    

    • 使用condition实现阻塞队列的例子
    package lock;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class BlockingQueue_Demo {
    
    
        public static void main(String[] args) throws InterruptedException {
            BlockingQueue kaneBlockingQueue = new BlockingQueue(6);
    
            new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        kaneBlockingQueue.put("x" + i);
                    }
                }
            }.start();
    
            Thread.sleep(1000L);
            System.out.println("开始取元素");
            for (int i = 0; i < 8; i++) {
                kaneBlockingQueue.take();
                Thread.sleep(2000);
            }
        }
    
    
    }
    
    class BlockingQueue {
    
        List<Object> list = new ArrayList<>();
        private Lock lock = new ReentrantLock();
        private Condition putCondition = lock.newCondition(); //condition可以有多个,针对不同的操作放入不同condition,相当于等待队列
        private Condition takeCondition = lock.newCondition();
        private int length;
    
        public BlockingQueue(int length) {
            this.length = length;
        }
    
        public void put(Object obj) {
            lock.lock(); //思考一个读一个写,为什么要加锁呢?
            try {
                while (true) {
                    if (list.size() < length) { //我们集合的长度不能超过规定的长度,才能向里面放东西
                        list.add(obj);
                        System.out.println("队列中放入元素:" + obj);
                        takeCondition.signal();
                        return;
                    } else { //如果放不进去,就该阻塞. --利用condition实现
                        putCondition.await();//挂起
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public Object take() {
            lock.lock();
            try {
                while (true) {
                    if (list.size() > 0) {
                        Object obj = list.remove(0);
                        System.out.println("队列中取得元素:" + obj);
                        putCondition.signal();
                        return obj;
                    } else {
                        takeCondition.await();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                return null;
            }
        }
    }
    
    队列中放入元素:x0
    队列中放入元素:x1
    队列中放入元素:x2
    队列中放入元素:x3
    队列中放入元素:x4
    队列中放入元素:x5
    开始取元素
    队列中取得元素:x0
    队列中放入元素:x6
    队列中取得元素:x1
    队列中放入元素:x7
    队列中取得元素:x2
    队列中放入元素:x8
    队列中取得元素:x3
    队列中放入元素:x9
    队列中取得元素:x4
    队列中取得元素:x5
    队列中取得元素:x6
    队列中取得元素:x7
    
    Process finished with exit code 0
    
    

    可重入锁 ReentrantLock

    一般来说,如果可重入锁的加锁次数是n,那么解锁次数也得是n才能完全释放锁,否则,如果小于n 则无法正常释放锁,此时如果有别的线程要加锁,则无法获取到锁而被阻塞;如果大于n,则会触发 IllegalMonitorStateException 异常, ReentrantLock 默认是使用非公平锁,如果要使用公平锁,可以使用 new ReentrantLock(true) 来创建。

    • 小于n的情况
    package lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Reentrant_Demo {
        static Lock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "获得第1次锁");
    
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "获得第2次锁");
    
            lock.unlock();
    
            new Thread() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开始去释放锁");
                    lock.lock();
                    System.out.println("获得锁成功~~~");
                    lock.unlock();
                }
            }.start();
        }
    }
    
    main获得第1次锁
    main获得第2次锁
    Thread-0开始去释放锁
    // 子线程获取锁失败导致阻塞了
    
    • 大于n的情况
      修改成3次unlock
            // 修改成3次unlock
            lock.unlock();
            lock.unlock();
            lock.unlock();
    
    main获得第1次锁
    main获得第2次锁
    Exception in thread "main" java.lang.IllegalMonitorStateException
        at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
        at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
        at lock.Reentrant_Demo.main(Reentrant_Demo.java:18)
    
    • 简单说明图
      简单说明图

    实现一个ReenrantLock的demo版本 - 一个现实思想的简单版本

    package lock;
    
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.LockSupport;
    
    public class ReentrantLock_Demo implements Lock {
        //记录锁的拥有者
        AtomicReference<Thread> owner = new AtomicReference<>();
    
        //记录重入次数的count
        AtomicInteger count = new AtomicInteger(0);
    
        //等待队列
        private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue();
    
        @Override
        public boolean tryLock() {
            //判断count值是否为0,如果count不等于0,说明锁被占用
            int ct = count.get();
            //判断锁是不是自己占用的,做重入
            if (ct != 0) {
                if (Thread.currentThread() == owner.get()) {
                    count.set(ct + 1);
                    return true;
                }
            } else { //若count为0 ,表示当前锁未被占用,通过CAS操作
                if (count.compareAndSet(ct, ct + 1)) {
                    owner.set(Thread.currentThread());          //如果不是自己,进入队列
                    return true;
                }
            }
            return false;
        }
    
    
        @Override
        public void lock() {
            if (!tryLock()) {
                //加入等待队列
                waiters.offer(Thread.currentThread());
    
                while (true) {
                    //若线程是队列头部,先判断一次,现在能不能去抢,然后再去加锁
                    Thread head = waiters.peek();
                    if (head == Thread.currentThread()) {
                        if (!tryLock()) {
                            LockSupport.park();
                        } else {
                            waiters.poll();
                            return;
                        }
                    } else {
                        LockSupport.park();
                    }
                }
    
            }
        }
    
        public boolean tryUnlock() {
            if (owner.get() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            } else {
                int ct = count.get();
                int nextc = ct - 1;
                count.set(nextc);
    
                if (nextc == 0) { //可重入锁被加锁多次,一旦为0 就释放锁,如果不是0,还得继续释放
                    owner.compareAndSet(Thread.currentThread(), null);
                    return true;
                } else {
                    return false;
                }
            }
        }
    
        @Override
        public void unlock() {
            if (tryUnlock()) {
                Thread head = waiters.peek();
                if (head != null) {
                    LockSupport.unpark(head);
                }
            }
        }
    
        /**
         * 暂时忽略
         *
         * @throws InterruptedException
         */
        @Override
        public void lockInterruptibly() throws InterruptedException {
    
        }
    
    
        /**
         * 暂时忽略
         *
         * @throws InterruptedException
         */
    
        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return false;
        }
    
        /**
         * 暂时忽略
         *
         * @throws InterruptedException
         */
        @Override
        public Condition newCondition() {
            return null;
        }
    }
    

    如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~

    相关文章

      网友评论

        本文标题:Java并发 - Lock接口

        本文链接:https://www.haomeiwen.com/subject/xwxhvrtx.html