• 听说你不会Lock,我发了3个夜晚写给你


    我们知道 synchronized 是java内部关键字,比较重量级的独占锁,好处就是使用方便,不需要手动释放锁;然而

    Lock 则需要手动加锁,手动释放锁;

    一ReentrantLock使用

    ReentrantLock 意为可重入锁,方法预览如下

    //创建一个 ReentrantLock 的实例
    ReentrantLock() 
     //创建一个具有给定公平策略的 ReentrantLock     
    ReentrantLock(boolean fair)
    //查询当前线程持有锁的个数
    int getHoldCount() 
    //返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null     
    protected Thread getOwner() 
    //返回一个collection,它包含可能正等待获取此锁的线程     
    protected Collection<Thread> getQueuedThreads() 
    int getQueueLength() //返回正等待获取此锁的线程估计数 
     //返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程     
    protected Collection<Thread> getWaitingThreads(Condition condition)
    //返回等待与此锁相关的给定条件的线程估计数    
    int getWaitQueueLength(Condition condition) 
    //查询给定线程是否正在等待获取此锁    
    boolean hasQueuedThread(Thread thread) 
    //查询是否有些线程正在等待获取此锁    
    boolean hasQueuedThreads() 
    //查询是否有些线程正在等待与此锁有关的给定条件    
    boolean hasWaiters(Condition condition) 
    //如果此锁的公平设置为 true,则返回true 
    boolean isFair() 
    //查询当前线程是否保持此锁    
    boolean isHeldByCurrentThread()
    //查询此锁是否由任意线程保持    
    boolean isLocked()
    //获取锁    
    void lock()
    //如果当前线程未被中断,则获取锁。    
    void lockInterruptibly() 
    //返回用来与此 Lock 实例一起使用的 Condition 实例     
    Condition newCondition() 
     //仅在调用时锁未被另一个线程保持的情况下,才获取该锁    
    boolean tryLock()
    //如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁 
    boolean tryLock(long timeout, TimeUnit unit)
    //释放此锁
    void unlock() 
    

    1.1Lock与unLock 简单使用

    使用 lock() 加锁, unlock()释放锁;

    public class RLook {
    
        private Lock lock = new ReentrantLock();
    
        private void testLock(){
            // 加锁
            lock.lock();
            // 执行业务逻辑
            for (int i=0; i<8; i++){
                System.out.println(i+"==="+Thread.currentThread().getName());
            }
            // 释放锁
            lock.unlock();
        }
    
        public static void main(String[] args) {
            // 线程1
            RLook rLook = new RLook();
            new Thread(()-> {
                rLook.testLock();
            }).start();
            // 线程2 
            new Thread(()-> {
                rLook.testLock();
            }).start();
        }
    }
    

    输出如下,不同线程之间应是分组打印;

    0===Thread-0
    1===Thread-0
    2===Thread-0
    3===Thread-0
    4===Thread-0
    5===Thread-0
    6===Thread-0
    7===Thread-0
    0===Thread-1
    1===Thread-1
    2===Thread-1
    3===Thread-1
    4===Thread-1
    5===Thread-1
    6===Thread-1
    7===Thread-1
    

    1.2Lock与unLock 正确使用方式

    上面的代码有个缺点,如果在执行业务代码的时候发生了异常就会发生死锁的现象,所以通常情况下我们会将业务代码放在try{},catch{} 代码块中, 最后使用 finally 释放锁;

    代码格式应如下

    Lock lock =new ReentrantLock();
    ....
    lock.lock();
    try{
        //处理任务
    }catch(Exception ex){
         
    }finally{
        lock.unlock();   //释放锁
    }
    ....
    

    1.3源码角度说明Lock

    public interface Lock {
    	// 加锁
        void lock();
        // 中断
        void lockInterruptibly() throws InterruptedException;
        // 尝试获取锁
        boolean tryLock();
        // 尝试获取锁
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        // 释放锁
        void unlock();
        // 信号通知
        Condition newCondition();
    }
    
    • lock()方法是用来获取锁。如果锁已被其他线程获取,则进行锁等待。采用Lock必须手动释放锁,并且在发生异常时,不会自动释放锁,所以需要放在try,cath代码块中, 最后使用 finally 释放锁;

    • tryLock()方法用来表示尝试获取锁,如果获取成功,则返回true,如果获取失败,则返回false;

    • tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,区别在于可以设置锁等待时间,在等待时间内为获取到锁,则返回false,获取到则返回true;

    Lock lock=new ReentrantLock();
    ....
    if(lock.tryLock()) {
         try{
             //业务逻辑
         }catch(Exception ex){
             
         }finally{
         //释放锁
             lock.unlock();   
         } 
    }else {
        // 未获取锁逻辑
    }
    
    • lockInterruptibly()为中断方法,当通过这个方法去获取锁时,如果线程正在处于获取锁状态,则该线程能够响应中断(中断线程的等待状态),抛出中断异常。也就使说,当两个线程同时通过lock.lockInterruptibly()获取某个锁时,如果线程A获取到了锁,而线程B在等待状态,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待状态;
    • Condition类是JDK5的新API,可以实现多路通知功能,一个Lock对象可以创建多个Condition, 通过注入不同的Condition灵活实现不同的线程通知功能;然而synchronized 的 wait(), notify(), notify All() 通知机制是随机无法实现选择性通知功能;

    1.4使用lockInterruptibly

    public class InterruptTest {
    
        private Lock lock = new ReentrantLock();
    
        public void interrupt() throws InterruptedException {
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + "得到了锁");
                long startTime = System.currentTimeMillis();
    
                for (; ; ) {
                    if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                        break;
                }
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放锁");
            }
        }
    
        public static void main(String[] args) {
            // 线程1
            InterruptTest rLook = new InterruptTest();
            Thread threadA = new Thread(() -> {
                try {
                    rLook.interrupt();
                } catch (InterruptedException e) {
                    System.out.println("线程A进行了中断");
                    e.printStackTrace();
                }
            });
            // 线程2
            Thread threadB = new Thread(() -> {
                try {
                    rLook.interrupt();
                } catch (InterruptedException e) {
                    System.out.println("线程B进行了中断");
                    e.printStackTrace();
                }
            });
            threadA.setName("A");
            threadB.setName("B");
            threadA.start();
            threadB.start();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadB.interrupt();
        }
    }
    

    输出结果如下,线程A通过中断获取锁,线程B再通过中断获取锁时处于等待状态,直接中断抛出异常;

    A得到了锁
    线程B进行了中断
    

    1.5使用Condition实现等待通知机制

    codition 接口主要方法如下

     //造成当前线程在接到信号或被中断之前一直处于等待状态。 
     void await() 
     //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
     boolean await(long time, TimeUnit unit) 
     //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
     long awaitNanos(long nanosTimeout) 
     //造成当前线程在接到信号之前一直处于等待状态。 
     void awaitUninterruptibly() 
     //造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 
     boolean awaitUntil(Date deadline) 
     //唤醒一个等待线程。
     void signal()  
     //唤醒所有等待线程。
     void signalAll()  
    

    利用 Condition 的await() 方法和 signal() 方法 实现 等待通知机制;

    public class ConditionTest {
    
        private Lock lock = new ReentrantLock();
    
        private Condition condition = lock.newCondition();
    
        // 线程等待
        private void await(){
            try {
                // 加锁
                lock.lock();
                System.out.println("await时间为:"+System.currentTimeMillis());
                // 等待
                condition.await();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        // 线程唤醒
        public void signal() {
            lock.lock();
            try {
                System.out.println("signal时间为" + System.currentTimeMillis());
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 线程1
            ConditionTest rLook = new ConditionTest();
            Thread thread = new Thread(() -> {
                rLook.await();
            });
            thread.start();
            //
            thread.sleep(3000);
            //
            rLook.signal();
        }
    }
    

    输出输出结果如下

    await时间为:1606707478993
    signal时间为1606707481993
    

    多个Condition 使用方式

    多个Condition 使用时,互不干涉; 线程 A, B 使用 不同Condition 的都 进入等待状态, 当使用 对应的 Condition 的 singal 才唤醒对应的线程;

    public class MultiCondition {
    
        private Lock lock = new ReentrantLock();
    
        private Condition conditionA = lock.newCondition();
        private Condition conditionB = lock.newCondition();
    
        // 线程等待
        private void awaitA(){
            try {
                // 加锁
                lock.lock();
                System.out.println("线程"+Thread.currentThread().getName()+"----await时间为:"+System.currentTimeMillis());
                // 等待
                conditionA.await();
                System.out.println("线程"+Thread.currentThread().getName()+"继续执行");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        // 线程等待
        private void awaitB(){
            try {
                // 加锁
                lock.lock();
                System.out.println("线程"+Thread.currentThread().getName()+"----await时间为:"+System.currentTimeMillis());
                // 等待
                conditionB.await();
                System.out.println("线程"+Thread.currentThread().getName()+"继续执行");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        // 线程唤醒
        public void signalAllA() {
            lock.lock();
            try {
                System.out.println("线程"+Thread.currentThread().getName()+"----signal时间为" + System.currentTimeMillis());
                conditionA.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        // 线程唤醒
        public void signalAllB() {
            lock.lock();
            try {
                System.out.println("线程"+Thread.currentThread().getName()+"----signal时间为" + System.currentTimeMillis());
                conditionB.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 线程1
            MultiCondition rLook = new MultiCondition();
            Thread threadA = new Thread(() -> {
                rLook.awaitA();
            });
            threadA.setName("A");
            threadA.start();
            Thread threadB = new Thread(() -> {
                rLook.awaitB();
            });
            threadB.setName("B");
            threadB.start();
            //
            Thread.sleep(3000);
            //
            rLook.signalAllA();
        }
    }
    

    输出结果如下,线程Condition A 对应的线程会被唤醒,B 线程 还是处于等待状态;

    线程A----await时间为:1606708895650
    线程B----await时间为:1606708895650
    线程main----signal时间为1606708898651
    线程A继续执行
    

    1.6使用Condition实现生产消费模式

    public class ConsumerProductCondition {
    
        private Lock lock = new ReentrantLock();
    
        private Condition condition = lock.newCondition();
    
        private Boolean has = false;
    
        // 生产
        private void set(){
            try {
                // 加锁
                lock.lock();
                while (has == true){
                    condition.await();
                }
                System.out.println("生产GG");
                has = true;
                // 唤醒一个线程
                condition.signal();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        // 消费
        private void get(){
            try {
                // 加锁
                lock.lock();
                while (has == false){
                    condition.await();
                }
                System.out.println("消费MM");
                has = false;
                // 唤醒一个线程
                condition.signal();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 线程1
            ConsumerProductCondition rLook = new ConsumerProductCondition();
            Thread threadA = new Thread(() -> {
                for (int i = 0; i < 200; i++) {
                    rLook.set();
                }
            });
           // 线程2
            Thread threadB = new Thread(() -> {
                for (int i = 0; i < 200; i++) {
                    rLook.get();
                }
            });
            threadA.start();
            threadB.start();
        }
    }
    

    输出交替打印

    生产GG
    消费MM
    生产GG
    消费MM
    生产GG
    消费MM
    生产GG
    消费MM
    生产GG
    消费MM
    .....
    

    1.7 公平锁与非公平锁

    Lock 锁分为公平锁和非公平锁,公平锁表示通过线程加锁的顺序获取锁,非公平锁表示通过抢占机制获取锁;

    公平锁获示例

    public class FairLockTest {
    
    
        private Lock lock = new ReentrantLock(true);
    
        private void testLock(){
            // 加锁
            lock.lock();
            // 执行业务逻辑
            System.out.println("线程"+Thread.currentThread().getName()+"获取到锁");
            // 释放锁
            lock.unlock();
        }
    
        public static void main(String[] args) {
            // 线程1
            FairLockTest rLook = new FairLockTest();
    
    
            for (int i = 0; i <100 ; i++) {
                // 线程2
                Thread thread = new Thread(() -> {
                    rLook.testLock();
                });
                thread.setName(""+i);
                thread.start();
            }
    
        }
    
    }
    

    输出结果基本有序

    线程0获取到锁
    线程1获取到锁
    线程2获取到锁
    线程3获取到锁
    线程4获取到锁
    线程5获取到锁
    线程6获取到锁
    线程8获取到锁
    线程7获取到锁
    线程9获取到锁
    线程10获取到锁
    ....
    

    非公平锁示例如下

    public class FairLockTest {
    
    
        private Lock lock = new ReentrantLock(false);
    
        private void testLock(){
            // 加锁
            lock.lock();
            // 执行业务逻辑
            System.out.println("线程"+Thread.currentThread().getName()+"获取到锁");
            // 释放锁
            lock.unlock();
        }
    
        public static void main(String[] args) {
            // 线程1
            FairLockTest rLook = new FairLockTest();
    
    
            for (int i = 0; i <100 ; i++) {
                // 线程2
                Thread thread = new Thread(() -> {
                    rLook.testLock();
                });
                thread.setName(""+i);
                thread.start();
            }
    
        }
    
    }
    

    输出结果基本无序

    线程0获取到锁
    线程1获取到锁
    线程7获取到锁
    线程2获取到锁
    线程10获取到锁
    线程3获取到锁
    线程6获取到锁
    线程11获取到锁
    线程4获取到锁
    线程5获取到锁
    .........
    

    二 ReentrantReadWriteLock 使用

    ReentrantLock 与 synchronized 都是 独占锁, 安全性较高,但是相对来说效率低下;ReentrantReadWriteLock读写锁提供了 readLock()和writeLock()用来获取读锁和写锁; 读锁之间读取数据不互斥(故读锁也成为共享锁)。读锁写锁之间互斥,写锁写锁之间互斥;

    2.1读锁

    读锁示例

    public class ReadLockTest {
    
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
        private void testLock(){
            // 加锁
            lock.readLock().lock();
            // 执行业务逻辑
            System.out.println("线程"+Thread.currentThread().getName()+"获取到锁"+System.currentTimeMillis());
            // 睡眠,保证B线程进来时A线程还是获取锁状态
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 释放锁
            lock.readLock().unlock();
        }
    
        public static void main(String[] args) {
            // 线程1
            ReadLockTest rLook = new ReadLockTest();
            // 线程2
            Thread threadA = new Thread(() -> {
                rLook.testLock();
            });
            threadA.setName("A");
            Thread threadB = new Thread(() -> {
                rLook.testLock();
            });
            threadB.setName("B");
            threadA.start();
            threadB.start();
    
    
        }
    }
    

    线程A与线程B几乎时同时获取到锁

    线程B获取到锁1606723001641
    线程A获取到锁1606723001641
    

    2.2 写锁

    写锁示例

    public class WriteLockTest {
    
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
        private void testLock(){
            // 加锁
            lock.writeLock().lock();
            // 执行业务逻辑
            System.out.println("线程"+Thread.currentThread().getName()+"获取到锁"+System.currentTimeMillis());
            // 睡眠,保证B线程进来时A线程还是获取锁状态
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 释放锁
            lock.writeLock().unlock();
        }
    
        public static void main(String[] args) {
            // 线程1
            WriteLockTest rLook = new WriteLockTest();
            // 线程2
            Thread threadA = new Thread(() -> {
                rLook.testLock();
            });
            threadA.setName("A");
            Thread threadB = new Thread(() -> {
                rLook.testLock();
            });
            threadB.setName("B");
            threadA.start();
            threadB.start();
    
    
        }
    }
    

    输出结果基本就是相差 2 秒,即线程A获取锁后,线程B需要等线程A释放锁才能获取锁

    线程A获取到锁1606723216664
    线程B获取到锁1606723218664
    

    2.3 读写锁

    读写锁示例

    public class ReadAndWriteTest {
    
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
        private void writeLock(){
            // 加锁
            lock.writeLock().lock();
            // 执行业务逻辑
            System.out.println("线程"+Thread.currentThread().getName()+"获取到写锁"+System.currentTimeMillis());
            // 睡眠,保证B线程进来时A线程还是获取锁状态
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 释放锁
            lock.writeLock().unlock();
        }
    
        private void readLock(){
            // 加锁
            lock.readLock().lock();
            // 执行业务逻辑
            System.out.println("线程"+Thread.currentThread().getName()+"获取到读锁"+System.currentTimeMillis());
            // 释放锁
            lock.readLock().unlock();
        }
    
        public static void main(String[] args) {
            // 线程1
            ReadAndWriteTest rLook = new ReadAndWriteTest();
            // 线程2
            Thread threadA = new Thread(() -> {
                rLook.writeLock();
            });
            threadA.setName("A");
            Thread threadB = new Thread(() -> {
                rLook.readLock();
            });
            threadB.setName("B");
            threadA.start();
            threadB.start();
    
    
        }
    }
    

    输出结果如下,刚好相差2秒;当线程A获取到写锁,线程B必须等待线程A释放写锁后才能获取到读锁;

    线程A获取到写锁1606724500217
    线程B获取到读锁1606724502217
    

    三 总结

    通过本篇文章,我们大概知道常见的锁的使用方式;

    • ReentrantLock 和 synchronized 都是 独占锁,又是可重入锁;独占锁想必大家都知道,重点解释下可重入锁,

    当线程A获取到锁进入 methodA 时,再次调用 methodB 就不需要再次获取锁,即代表可重入锁;

    class Test{
        public synchronized void methodA() {
            methodB();
        }
         
        public synchronized void methodB() {
             
        }
    }
    
    • synchronized不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,线程B由于等待时间过长去处理其它业务,就称线程B获得的锁为可中断锁;

    • 公平锁尽量以请求锁的顺序来获取锁。比如有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这就是公平锁;非公平锁获取方式就是完全随机,抢占模式;

    • 读写锁一般用于操作文件,只要涉及到写锁都是独占锁;读锁是共享锁;

    有关 其它锁的知识,可以阅读知识追寻者以前发布的并发编程系列文章

    欢迎关注我的公众号:知识追寻者,送原创PDF,面经,开源系统

  • 相关阅读:
    DVI与DVI-D的区别
    easyui.combotree.search.js
    显示实时日期时间(html+js)
    Jquery 内容简介
    EasyUI 格式化DataGrid列
    EasyUI DataGrid 添加排序
    EasyUI DataGrid 复选框
    EasyUI 自定义DataGrid分页
    EasyUI DataGrid能编辑
    EasyUI 我的第一个窗口
  • 原文地址:https://www.cnblogs.com/zszxz/p/14072251.html
Copyright © 2020-2023  润新知