• Java中锁的使用及死锁、快速幂


    Java中锁的使用及死锁

    1 Java中锁的使用

    1.1 synchronized

    ①修饰一个代码块,被修饰的代码称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
    【修饰代码块】

    ②修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
    【修饰方法】

    ③修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。【修饰静态方法】

    ④修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
    【修饰类】

    1.1.1 sync修饰代码块

    一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞

    package demo;
    
    public class Sync1 {
    
        public static void main(String[] args) {
            System.out.println("使用sync修饰代码块");
            SyncThread syncThread = new SyncThread();
            Thread threadA = new Thread(syncThread, "threadA");
            Thread threadB = new Thread(syncThread, "threadB");
            threadA.start();
            threadB.start();
        }
    
    
    }
    class SyncThread implements Runnable {
    
        private static int count;
    
        public SyncThread(){
            count = 0;
        }
    
        @Override
        public void run() {
            synchronized (this){
                for(int i = 0; i < 5; i++){
                    try{
                        System.out.println("线程名:" + Thread.currentThread().getName() + ":" + (count++));
                        Thread.sleep(100);
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public int getCount(){
            return count;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    效果:
    在这里插入图片描述

    在定义接口方法时不能使用synchronized关键字。
    构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
    
    • 1
    • 2
    1.1.2 sync修饰方法

    new Thread()的时候使用同一个对象重写Thread的run

    public class Sync2 {
    
        public static void main(String[] args) {
            //使用同一个对象syncThread2
            SyncThread2 syncThread2 = new SyncThread2();
            Thread threadA = new Thread(() -> syncThread2.run(), "threadA");
            Thread threadB = new Thread(() -> syncThread2.run(),  "threadB");
            threadA.start();
            threadB.start();
        }
    }
    
    class SyncThread2 implements Runnable{
        static int count = 0;
        @Override
        public synchronized void run() {
            for(int i = 0; i < 5; i++){
                System.out.println("线程名:" + Thread.currentThread().getName() + ":" + (count++));
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    synchronized修饰普通方法的时候,锁的是this对象,锁的是当前调用方法的对象
    - 因此用同一个对象调用的时候是同步的
    - 用两个不同的对象调用,不能保证同步,因为不是同一把锁
    
    • 1
    • 2
    • 3
    1.1.3 sync修饰静态方法

    锁的是类的class【Sync3.class】

    public class Sync3 {
        static int count = 0;
    
        //用sync修饰静态方法
        public synchronized static void run() {
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println("线程名:" + Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            //使用不同对象访问
            Sync3 sync1 = new Sync3();
            Sync3 sync2 = new Sync3();
            Thread threadA = new Thread(() -> sync1.run(), "threadA");
            Thread threadB = new Thread(() -> sync2.run(), "threadB");
            threadA.start();
            threadB.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    1.1.4 sync修饰一个类

    锁的是类的class字节码,因此虽然是两个不同的对象调用方法,但是都是同一个类,共用一把锁

    public class Sync4 {
        int count = 0;
        public static void main(String[] args) {
            Sync4 sync1 = new Sync4();
            Sync4 sync2 = new Sync4();
            new Thread(() -> sync1.run(), "threadA").start();
            new Thread(() -> sync2.run(), "threadB").start();
        }
    
        public void run(){
            synchronized (Sync4.class){
                System.out.println("sync修饰类:锁住类的class字节码-----");
                for(int i = 0; i < 5; i++){
                    System.out.println("当前线程名:" + Thread.currentThread().getName() + ":" + (count++));
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    1.2 Lock锁

    JDK1.5之后新增的Lock接口及相关实现类

    相比于synchronized来说,Lock锁更加的灵活,可以控制什么时候获取锁,什么时候释放锁

    实现类:ReentrantLock、ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock

    因为Lock锁是接口,因此使用的时候要结合它的实现类,finally语句块是保证获取到锁之后,锁能够最终被释放

    Lock lock = new ReentrantLock();
    lock.lock();
    try{
    }finally{
    	lock.unlock();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    【注意:最好不要把锁的获取过程卸载try语句块中,因为如果在获取锁时发生了异常,异常抛出的同时也会导致锁不容易释放】

    1.2.1 Lock锁的基础使用

    注意:由于Lock锁过于复杂,此处仅展示基础用法,详细说明不在此叙述

    public class LockDemo {
    
        private Lock lock = new ReentrantLock();
    
        static int count = 0;
    
        public static void main(String[] args) {
            LockDemo lockDemo = new LockDemo();//因为此处lock锁是类属性,所以为需要用同一个对象
    
            Thread threadA = new Thread(() -> lockDemo.run(),"threadA");
            Thread threadB = new Thread(() -> lockDemo.run(), "threadB");
            threadA.start();
            threadB.start();
    
        }
    
        public void run() {
            lock.lock();
            try {
                System.out.println("Lock锁演示....");
                for (int i = 0; i < 5; i++) {
                    System.out.println("线程名:" + Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println("==========");
                lock.unlock();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述

    其他用法:

    • tryLock
    • interruptlock
    • delay

    详细见:https://blog.csdn.net/caideb/article/details/85289790

    1.3 Lock锁与synchronized特点及区别

    【1】synchronized特点:

    • synchronized具有同步功能,是一种互斥锁。synchronized修饰普通方法的时候,锁的是this,是调用方法的对象。修饰静态方法的时候,锁的是字节码文件。
    • synchronized可以用来修饰代码块和方法
    • synchronized可以保证原子性,有序性,可见性。(volatile不能保证原子)
    • synchronized底层由JVM实现,不能手动控制锁的释放,不如lock锁灵活,synchronized修饰的方法一旦出现异常,JVM保证锁会被释放(Lock锁需要在finally中释放)
    • synchronized是非公平锁,不保证公平性。

    【2】Lock锁特点:

    • 尝试非阻塞地获取锁
    • 能被中断地获取锁
    • 超时获取锁
    • 发生异常不会释放锁

    【3】区别

    • sync是关键字,内置语言实现,Lock是接口
    • sync在线程发生异常时会自动释放,不会发生死锁。Lock异常时不会自动释放,需要在finally中手动释放
    • Lock是可中断锁,sync是非中断锁,必须等线程执行完成才释放锁
    • Lock可以使用读锁来提高多线程读效率

    2 死锁问题

    2.1 手写一个死锁

    思路:
    ①创建两个字符串a和b
    ②创建两个线程A和B
    ③让每个线程都用synchronized锁住字符串(A先锁字符串a,再去锁b;线程B先锁字符串b再锁a)
    ④如果A锁住a,B锁住b,A就无法锁住b,B也无法再锁住a,这个时候就产生了死锁

    
    public class MyDeadLock {
        public static String obj1 = "obj1";
        public static String obj2 = "obj2";
    
        public static void main(String[] args) {
            //线程A先获取obj1
            Thread threadA = new Thread(new Lock1());
            //线程B先获取obj2
            Thread threadB = new Thread(new Lock2());
            threadA.start();
            threadB.start();
        }
    
    
    }
    
    class Lock1 implements Runnable {
    
        @Override
        public void run() {
            try {
                System.out.println("Lock1 running...");
                while (true) {
                    //以字符串obj1为锁
                    synchronized (MyDeadLock.obj1) {
                        System.out.println("Lock1 lock obj1");
                        //获取到obj1资源后先让线程A等一会,让Lock2有足够的时间锁住obj2
                        Thread.sleep(3000);
                        //线程A尝试获取obj2
                        synchronized (MyDeadLock.obj2) {
                            System.out.println("Lock1 lock obj2");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    class Lock2 implements Runnable {
    
        @Override
        public void run() {
            try {
                System.out.println("Lock2 is running");
                while (true) {
                    //获取obj2
                    synchronized (MyDeadLock.obj2) {
                        System.out.println("Lock2 lock obj2");
                        Thread.sleep(3000);
                        //尝试获取obj1
                        synchronized (MyDeadLock.obj1) {
                            System.out.println("Lock2 lock obj1");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    测试死锁:
    在这里插入图片描述

    我们也可以通过Lambda方式实现死锁

    public class DeadLockDemo2 {
    
        //使用Lambda方式实现
        //以DeadLockDemo2.class 和 Object.class分别作为两个互斥资源
        public static void main(String[] args) {
    
            new Thread(() -> {
                System.out.println("threadA is running----------");
                try {
                    synchronized (DeadLockDemo2.class) {
                        System.out.println("threadA is get obj1");
                        Thread.sleep(3000);
                        synchronized (Object.class) {
                            System.out.println("threadA is get obj2");
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
    
            new Thread(() -> {
                System.out.println("threadB is running---------");
                try {
                    synchronized (Object.class) {
                        System.out.println("threadB is get obj2");
                        Thread.sleep(3000);
                        synchronized (DeadLockDemo2.class) {
                            System.out.println("threadB is get obj1");
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述
    最简单方式实现死锁

    public class DeadLockDemo {
    
        //资源1
        private static Object resources1 = new Object();
        //资源2
        private static Object resources2 =new Object();
    
        public static void main(String[] args) {
            new Thread(()->{
                synchronized (resources1){
                    System.out.println(Thread.currentThread().getName() + " get resources1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " waiting get resources2");
                    synchronized (resources2){
                        System.out.println(Thread.currentThread().getName() + " get resources2");
                    }
                }
    
            }, "thread1").start();
    
            new Thread(()->{
                synchronized (resources2){
                    System.out.println(Thread.currentThread().getName() + " get resources2");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " waiting get resources1");
                    synchronized (resources1){
                        System.out.println(Thread.currentThread().getName() + " get resources1");
                    }
                }
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    解决死锁之一— —破坏循环等待条件:

    public class DeadLockDemo {
    
        //资源1
        private static Object resources1 = new Object();
        //资源2
        private static Object resources2 =new Object();
    
        public static void main(String[] args) {
            new Thread(()->{
                synchronized (resources1){
                    System.out.println(Thread.currentThread().getName() + " get resources1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " waiting get resources2");
                }
                //破坏循环等待条件
                synchronized (resources2){
                    System.out.println(Thread.currentThread().getName() + " get resources2");
                }
    
            }, "thread1").start();
    
            new Thread(()->{
                synchronized (resources2){
                    System.out.println(Thread.currentThread().getName() + " get resources2");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " waiting get resources1");
                }
                //破坏循环等待,不要嵌套sync
                synchronized (resources1){
                    System.out.println(Thread.currentThread().getName() + " get resources1");
                }
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    2.2 查看java进程中是否包含死锁(排查死锁)

    ①通过在cms窗口输入jsp命令获取到当前正在运行的java进程
    在这里插入图片描述
    这个时候我们怀疑进程号为14648的Java进程存在问题。

    ②通过jstack+PID(进程号)判断该进程是否存在死锁

    在命令行输入:
    jstack 14648
    
    • 1
    • 2

    在这里插入图片描述
    可以发现成功发现1个死锁

    2.3 死锁产生原因及条件

    • 死锁产生原因:
    1. 系统资源不足
    2. 进程运行推进的顺序不合适
    3. 资源分配不当
    
    "如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则可能会因
    争夺有限的资源而陷入死锁。"
    
    "其次,如果进程运行推进的顺序与速度不同,也可能产生死锁"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 产生死锁的四个必要条件
    1. 互斥条件:一个资源每次只能被一个进程使用
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
    
    "只要上述4个条件有一个不满足,就不会发生死锁"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.4 死锁的解除与预防

    既然上述4个条件必须全部满足,那么我们可以通过破坏其中之一来解除死锁
    
    1)破坏"请求和保持"条件
    让进程不要那么`贪心`,自己已经有资源了就不要去竞争那些不可抢占的资源。
    如:
    ①让进程一次性申请所有需要用到的资源,不要一次一次申请,当申请的资源没有空时,就让线程等
    待,但是这个方法比较浪费资源,线程可能经常处于饥饿状态。
    ②要求进程在申请资源前,释放自己拥有的所有资源
    
    2)破坏"不可抢占"条件
    允许进程进行抢占
    ①如果线程去抢资源,被拒绝(失败),就释放自己的资源
    ②操作系统允许抢,只要你优先级大,你就可以抢到
    
    3)破坏"循环等待"条件
    将系统中的所有资源进行统一编号,进程可以在任何时刻提出资源申请,但所有申请必须按照资源
    的编号顺序(升序)提出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.5 死锁的监测

    1. 每个进程、每个资源制定唯一编号

    2. 设定一张资源分配表,记录各进程与占用资源之间的关系
      在这里插入图片描述

    3. 设置一张进程等待表,记录各进程与要申请资源之间的关系
      在这里插入图片描述

    3 拓展(快速幂)

    3.1 概念

    如果我们要求一个数的n次方,我们会怎么求呢?
    有人会说直接循环,每次相乘就行了

    这个我们通常的写法:

    //求base^pow
    public static int power1(int base, int pow) {
        int result = 1;
        for (int i = 0; i < pow; i++) {
            result *= base;
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样写的时间复杂度为O(n),如果数字过大,会比较消耗时间,那么有没有什么方法能够降低时间复杂度的呢?这个时候就需要用到快速幂了,它能够将时间复杂度降低到O(logn)

    比如:我们要求a^b
    那么如何通过快速幂求取正确的值呢?
    
    如果b是偶数:a ^ b =  (a ^ 2) ^ (b / 2);
    如果b是奇数:a ^ b = a * (a ^ 2) ^ (b / 2);
    ...一直分解下去,直到最后的幂为0或者1
    "由此可以看出之前我们需要乘b次,现在我们逐个分解下去,直到最后幂为0或1"
    -- 时间复杂度由原来的O(n)变为了O(logn)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.2 实现

    • 递归版本
    public static int quickPower(int base, int pow) {
        int result = 1;
        while (pow > 0) {
            if (pow % 2 == 0) {
                //幂为偶数 直接减半 底数(base)平方
                pow /= 2;
                base *= base;
            } else {
                //幂为奇数 会多出来一个数 需要记录下来
                pow /= 2;
                result *= base;
                base *= base;
            }
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 位运算(常数时间优化)
    public static int quickPower2(int base, int pow) {
        int result = 1;
        while (pow > 0) {
            if ((pow & 1) == 1) {
                //奇数
                result *= base;
            }
            //偶数
            pow >>= 1;
            base *= base;
        }
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    告警收敛杂谈
    Asp.net core 少走弯路系列教程(cnblogs 博客园首发)
    Spring源码------IOC容器初始化过程
    【揭秘Vue】nextTick的神秘面纱:原理与作用一览无余!
    多层感知机与DNN算法
    CVF_统一多模态之文字生成
    探索云世界的无限可能
    Jmeter配置脚本录制进行抓包并快速分析、定位接口问题
    HaaS学习笔记 | HaaS框架环境下基于MicroPython的LED跑马灯实现及比较
    Avanci与现代汽车和起亚签署专利许可协议
  • 原文地址:https://blog.csdn.net/weixin_45565886/article/details/126920713