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;
    }
}

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

在定义接口方法时不能使用synchronized关键字。
构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
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();
            }
        }
    }
}

在这里插入图片描述

synchronized修饰普通方法的时候,锁的是this对象,锁的是当前调用方法的对象
- 因此用同一个对象调用的时候是同步的
- 用两个不同的对象调用,不能保证同步,因为不是同一把锁
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.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 Lock锁

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

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

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

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

Lock lock = new ReentrantLock();
lock.lock();
try{
}finally{
	lock.unlock();
}

【注意:最好不要把锁的获取过程卸载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();
        }
    }
}

在这里插入图片描述

其他用法:

  • 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();
        }
    }
}

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

我们也可以通过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();
    }
}

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

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();
    }
}

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

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();
    }
}

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

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

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

在命令行输入:
jstack 14648

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

2.3 死锁产生原因及条件

  • 死锁产生原因:
1. 系统资源不足
2. 进程运行推进的顺序不合适
3. 资源分配不当

"如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则可能会因
争夺有限的资源而陷入死锁。"

"其次,如果进程运行推进的顺序与速度不同,也可能产生死锁"
  • 产生死锁的四个必要条件
1. 互斥条件:一个资源每次只能被一个进程使用
2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

"只要上述4个条件有一个不满足,就不会发生死锁"

2.4 死锁的解除与预防

既然上述4个条件必须全部满足,那么我们可以通过破坏其中之一来解除死锁

1)破坏"请求和保持"条件
让进程不要那么`贪心`,自己已经有资源了就不要去竞争那些不可抢占的资源。
如:
①让进程一次性申请所有需要用到的资源,不要一次一次申请,当申请的资源没有空时,就让线程等
待,但是这个方法比较浪费资源,线程可能经常处于饥饿状态。
②要求进程在申请资源前,释放自己拥有的所有资源

2)破坏"不可抢占"条件
允许进程进行抢占
①如果线程去抢资源,被拒绝(失败),就释放自己的资源
②操作系统允许抢,只要你优先级大,你就可以抢到

3)破坏"循环等待"条件
将系统中的所有资源进行统一编号,进程可以在任何时刻提出资源申请,但所有申请必须按照资源
的编号顺序(升序)提出

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;
}

这样写的时间复杂度为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)

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;
}
  • 位运算(常数时间优化)
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值