Day292&293&294&295.Lock锁 -Juc

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day292&293&294&295.Lock锁 -Juc相关的知识,希望对你有一定的参考价值。

Lock锁

一、Lock接口

1、Lock简介&地位&作用

  • 锁是一种工具,用于控制对共享资源的访问
  • Lock并不是用来替代sychronized的,而是当使用sychronized不适合或不满足要求的时候,来提供高级功能的。
  • Lock接口最常见的实现类就是ReentrantLock

2、为什么需要Lock?

为什么sychronized不够用

  • 效率低

锁的释放情况少、试图获取锁时不能设定超时、不能中断一个正在试图获取锁的线程

  • 不够灵活

加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的

  • 无法知道是否成功获取到锁

3、Lock主要方法

lock()、tryLock()、tryLock(Long time,TimeUnit unit)和lockInterruptibly()

1)Lock

  • lock()就是获取锁。如果锁已经被其他线程获取,则进行等待
  • Lock不会像sychronized一样,在发生异常时自动释放锁
  • 因此最佳实践是,在finally中释放锁,以保证发生异常时锁一定被释放
/******
 @author 阿昌
 @create 2021-06-08 21:17
 *******
 *      Lock不会像sychronized一样,在发生异常时,自动释放锁
 */
public class MustUnlock {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        //加锁
        lock.lock();
        try {
            //获取本锁保护的资源
            System.out.println(Thread.currentThread().getName()+": 开始执行任务");
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

image-20210608212004083

  • lock()方法不能被中断,一旦陷入死锁,lock将陷入永久等待

2)tryLock

  • tryLock()用来尝试获取锁,如果当前线程没有被其他线程占用,则获取成功,则返回true,否则返回false,代表获取锁失败

  • 相比上面的lock,他可以返回一个值,让我们知道是否成功获取到锁;进而决定后续程序的行为

  • 它会立刻返回,即便在拿不到锁时,不会一直等待


3)tryLock(long time,TimeUnit unit)

设置超时时间来设置放弃时间

  • 通过tryLock避免死锁

在try/finally中的finally中设置unlock解锁,保证抢不到锁就释放锁

/******
 @author 阿昌
 @create 2021-06-08 21:26
  *******
  *      用tryLock避免死锁
 */
public class TryLockDeadLock implements Runnable {
    int flag = 1;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        TryLockDeadLock t1 = new TryLockDeadLock();
        TryLockDeadLock t2 = new TryLockDeadLock();

        t1.flag=1;
        t2.flag=2;

        new Thread(t1).start();
        new Thread(t2).start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag == 1) {
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("线程1,获取lock1,成功");
                            Thread.sleep(new Random().nextInt(1000));

                            if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
                                try {
                                    System.out.println("线程1,获取lock2,成功");
                                    System.out.println("线程1,成功获取到了lock1、lock2");
                                    break;
                                }finally {
                                    lock2.unlock();
                                    Thread.sleep(new Random().nextInt(1000));
                                }
                            }else {
                                System.out.println("线程1,获取lock2,失败");
                            }
                        } finally {
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("线程1,获取lock1,失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (flag == 2) {
                try {
                    if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("线程2,获取lock2,成功");
                            Thread.sleep(new Random().nextInt(1000));

                            if (lock1.tryLock(3000,TimeUnit.MILLISECONDS)){
                                try {
                                    System.out.println("线程2,获取lock1,成功");
                                    System.out.println("线程2,成功获取到了lock1、lock2");
                                    break;
                                }finally {
                                    lock1.unlock();
                                    Thread.sleep(new Random().nextInt(1000));
                                }
                            }else {
                                System.out.println("线程2,获取lock1,失败");
                            }
                        } finally {
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("线程2,获取lock2,失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

image-20210608220711512


4)LockInterruptibly

image-20210608222415853

public class LockInterruptibly implements Runnable{

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockInterruptibly l = new LockInterruptibly();
        Thread thread0 = new Thread(l);
        Thread thread1 = new Thread(l);

        thread0.start();
        thread1.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread0.interrupt();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+": 尝试获取锁");
        try {
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName()+": 拿到了锁");
                Thread.sleep(5000);
            }catch (InterruptedException e){
                System.out.println("【睡眠期间】被中断");
            } finally{
                lock.unlock();
                System.out.println(Thread.currentThread().getName()+": 释放锁");
            }
        } catch (InterruptedException e) {
            System.out.println("【等锁期间】被中断");
            e.printStackTrace();
        }
    }
}

image-20210608222307571


4、锁的可见性保证

image-20210608222609358

image-20210608222744388


二、锁的分类

  • 这些分类,是从不同角度出发去看的

  • 这些分类并不是互斥的,也就是多个类型可以并存

有可能一个锁,同时属于两个类型

  • 比如ReentrantLock既是互斥锁,又是可重入锁

好比一个人可能同时是男人,又是军人

image-20210608223310261


1、乐观锁&悲观锁

1)为什么会诞生乐观锁(非互斥同步锁)

  • 悲观锁(互斥同步锁)的劣势

    • 阻塞和唤醒带来性能劣势
    • 永久阻塞

    如果持有的锁的线程被永久阻塞,比如遇到了无限循环、死锁等活跃性问题,那么等待该线程释放锁的那几个悲催的线程,将永远也得不到执行

    • 优先级反转

    若给线程设置了优先级,当一个比这个线程优先级低的线程拿到了这个悲观锁,如果他不释放,就需要一直等待,就算你优先级比他高,导致优先级错乱


2)什么是乐观锁&悲观锁

  • 是否锁住资源的角度分类

  • 悲观锁

image-20210608224807961

image-20210608224840827


  • 乐观锁
    • 认为自己在处理操作的时候不会有其他线程来干扰,所有并不会锁住被操作对象

    • 在更新的时候,去对比在我修改的期间数据有没有被其他人修改过:

      如果没有修改过,就说明真的是只有我自己在操作,那就正常去修改数据

    • 如果数据和我们一开始拿到的不一样了,说明其他人在这段那时间内修改过数据,那我们就不能继续刚才的更新操作,因此就会选择放弃、报错、重试等策略

    • 乐观锁的实现一般都是利用CAS算(一个原子操作内)法来实现

    • 例子

      • 原子类、并发容器
      • Git版本管理器
      • 数据库version字段

3)使用场景

悲观锁

  • 并发写入多的情况;
  • 临界区持锁时间长的情况
  • 避免大量无用自旋等消耗

image-20210609210749306


乐观锁

  • 并发写入少;

  • 大部分是读取场景,不加锁能让读取性能大幅提高


2、可重入&非可重入锁

ReentrantLock为例

1)使用案列

  • 买电影院座位

image-20210609211236322

//演示多线程预定电影院座位,一个座位不可能卖个2个人以上
public class CinemaBookSeat {
    private static Lock lock = new ReentrantLock();
    //预定座位
    private static void bookSeat(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+": 【开始】预定座位");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+": 【完成】预定座位");
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    //主函数
    public static void main(String[] args) {
        new Thread(()->bookSeat()).start();
        new Thread(()->bookSeat()).start();
        new Thread(()->bookSeat()).start();
        new Thread(()->bookSeat()).start();
    }
}

image-20210609211848605


  • 打印字符串
public class LockDemo {
    //主函数
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();

        lockDemo.init();
    }

    //初始化
    private void init(){
        Outputer outputer = new Outputer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(5);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    outputer.print("阿昌");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(5);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    outputer.print("三月");
                }
            }
        }).start();

    }

    static class Outputer{
        Lock lock = new ReentrantLock();

        //打印字符串
        public void print(String name){
            lock.lock();
            try{
                for (int i = 0; i < name.length(); i++) {
                    System.out.println(name.charAt(i));
                }
                System.out.println("");
            }finally {
                lock.unlock();
            }
        }
    }
}

image-20210609212547546


2)可重入性质

  • 什么是可重入

如果已经拿到锁了,那我之后可再次进入到这个锁里面

如果一个锁里面有两个方法,如果他不是可重入锁,那他执行完第一个方法后,需要释放锁,再去获取锁,才能执行第二个方法

  • 好处
    • 避免死锁
    • 提高封装性

3)代码演示

image-20210609214102897

image-20210609214050560


4)源码分析

可重入锁 & 非可重入锁的部分源码对比:↓↓↓

image-20210609214414125


5)其他方法

  • isHeldByCurrentThread

可以看出锁是否被当前线程持有

  • getQueueLength

可以返回当前正在等待这个把锁的队列有多长

这两个方法是开发和调试的时候使用较多,上线后一般用不到


3、公平锁&非公锁

1)什么是公平和非公平

公平:指的是按照线程请求的顺序,来分配锁;

非公平:指的是不完全按照请求的顺序,在一定情况下,可插队。

非公平也同样不提倡"插队"行为

这里的非公平,指的是“在合适的时机”插队,而不是盲目插队

什么是适合时机?

一个人排队买票,现在在第二位,当他成为第一位的时候,他脑子懵了,傻了2秒;

此时之前在第一位的人,突然出现,问:XXX点的车几点发车,很快的。问完就走,此时你脑子懵。

这个列子体现,插队的时机,就算他没插队,那个人也不能第一时间去有效的利用这个时机,因为脑子懵的


2)为什么要有非公平锁

image-20210609221802266


3)公平的情况(以ReentrantLock为例)

  • 如果在创建ReentrantLock对象时,参数填写为true,那么这就是公平锁

image-20210609222218792


4)不公平的情况(以ReentrantLock为例)

image-20210609222401111

image-20210609222507440


5)演示公平和非公平的效果

  • 公平锁的演示
/******
 @author 阿昌
 @create 2021-06-09 22:26
 *******
 *      演示公平&非公怕情况
 */
public class FairLock {

    public static void main(String[] args) {
        PrintQueue printQueue = new PrintQueue();
        Thread[] threadArr = new Thread[10];

        for (int i = 0; i < threadArr.length; i++) {
            threadArr[i] = new Thread(new Job(printQueue));
        }

        for (Thread thread : threadArr) {
            thread.start();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace()以上是关于Day292&293&294&295.Lock锁 -Juc的主要内容,如果未能解决你的问题,请参考以下文章

2021学习记录

转 : React Native 开发之 IDE 选型和配置

《安富莱嵌入式周报》第293期:SEGGER开源其C/C++库源码emRun,丰富EMC电磁兼容资,OTA开源组件,2022 Github全球报告,内存安全指南

学习010 blender293 blenderkit 插件 免费 下载 模型,材质,场景,全景图,笔划

第几天?(hdu2005)

比较好的网站地址