死锁和Lock锁

本文详细介绍了Java中死锁的概念、产生原因和一个典型的死锁示例,以及如何通过调整代码顺序避免死锁。同时,展示了使用Lock锁(如ReentrantLock)来替代synchronized关键字以提高并发性能和灵活性,并给出了Lock锁的使用示例,强调了Lock锁在处理线程安全问题时的优势和注意事项。

多个线程各自占有一些公共资源,并且互相等待其他线程占用的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题

死锁的代码示例如下所示:

package com.rgf;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
public class DeadLock {
    public static void main(String[] args) {
        Makeup G1 = new Makeup(0,"佳佳");
        Makeup G2 = new Makeup(1,"蕾蕾");
        G1.start();
        G2.start();
    }
}


//口红
class Lipstick{

}


//镜子
class Mirror{

}

class Makeup extends Thread{
    //需要的资源只有一份,用static来保证只有一份。
    static Lipstick lipstick=new Lipstick();
    static Mirror mirror=new Mirror();

    int choice;   //选择
    String girlName;  //使用化妆品的人
    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;

    }

    @Override
    public void run() {
       //化妆
        makeup();
    }
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup(){
        if (choice==0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(this.girlName+"获得口红的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (mirror){//一秒钟后想获得镜子
                    System.out.println(this.girlName+"获得镜子的锁");
            }
    }
}
        else{
            synchronized (mirror){//获得镜子的锁
                System.out.println(this.girlName+"获得镜子的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lipstick){//一秒钟后想获得口红
                    System.out.println(this.girlName+"获得口红的锁");
                }
            }
        }
        }
    }

运行界面如下所示: 

解决办法如下所示:

可以直接把代码块的锁拿出来即可,即放到同步外面,不让两个人同时抱一把锁,即两个人抱两把锁。

代码示例如下所示:

package com.rgf;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
public class DeadLock {
    public static void main(String[] args) {
        Makeup G1 = new Makeup(0,"佳佳");
        Makeup G2 = new Makeup(1,"蕾蕾");
        G1.start();
        G2.start();
    }
}


//口红
class Lipstick{

}


//镜子
class Mirror{

}

class Makeup extends Thread{
    //需要的资源只有一份,用static来保证只有一份。
    static Lipstick lipstick=new Lipstick();
    static Mirror mirror=new Mirror();

    int choice;   //选择
    String girlName;  //使用化妆品的人
    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;

    }

    @Override
    public void run() {
       //化妆
        makeup();
    }
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup(){
        if (choice==0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(this.girlName+"获得口红的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

    }
            synchronized (mirror){//一秒钟后想获得镜子
                System.out.println(this.girlName+"获得镜子的锁");
            }
}
        else{
            synchronized (mirror){//获得镜子的锁
                System.out.println(this.girlName+"获得镜子的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            synchronized (lipstick){//一秒钟后想获得口红
                System.out.println(this.girlName+"获得口红的锁");
            }
        }
        }
    }

运行界面如下所示: 

示例二:

package com.ypl.ticket;

/**
 * 演示线程的死锁问题
 * 死锁的理解:
 * 1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
 * 2.说明:
 * (1)出现思索后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
 * (2)我们使用同步时,要避免出现死锁。
 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1=new StringBuffer();
        StringBuffer s2=new StringBuffer();
        new Thread() {
            @Override
            public void run() {
               synchronized (s1){
                   s1.append("a");
                   s2.append("1");

                   try {
                       Thread.sleep(100);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   synchronized (s2){
                       s1.append("b");
                       s2.append("2");

                   System.out.println(s1);
                   System.out.println(s2);
    }
}
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
        }
    }

我们运行之后出现如下所示:
 

此时程序既不会运行也不会死亡,处于阻塞状态。 

示例三:

package com.ypl.ticket;

//死锁的演示
class A{
    public  synchronized  void  foo(B b){    //同步监视器:A类的对象:a
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了A实例的foo方法");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B实例的last方法");
        b.last();
    }

    public  synchronized  void last(){  //同步监视器:A类的对象:a
        System.out.println("进入了A类的last方法内部");
    }
}


class B{
    public synchronized void bar(A a){//同步监视器:B类的对象:b
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了B实例的bar方法");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A实例的last方法");
        a.last();
    }

    public  synchronized void last(){  //同步监视器:B类的对象:b
        System.out.println("进入了B类的last方法内部");
    }
}

public class DeadLock implements Runnable{
    A a=new A();
    B b=new B();
    public void init(){
        Thread.currentThread().setName("主线程");
        //调用a对象的foo方法
        a.foo(b);
        System.out.println("进入了主线程之后");
    }

    @Override
    public void run() {
        Thread.currentThread().setName("副线程");
        //调用b对象的bar方法
        b.bar(a);
        System.out.println("进入了副线程之后");
    }

    public static void main(String[] args) {
        DeadLock d1 = new DeadLock();
        new Thread(d1).start();
        //调用run方法:里面再调类B的方法bar,B类的bar方法再调A类的last方法,A类的last方法再调B类的last方法
        d1.init();
        //调用init方法:里面调用a对象的foo方法,a对象的foo方法调B类的last方法
    }
}

 运行之后如下所示:

死锁避免方法: 

产生死锁的四个必要条件:

1.互斥条件:一个资源每次只能被一个进程使用

2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生。

解决方法:

专门的算法、原则

尽量减少同步资源的定义

尽量避免嵌套同步

Lock(锁)

从JDK5.0开始,Java提供了更强大的线程同步机制----通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。synchronized是隐式的定义。

java.util.concurrent,locks.Lock接口是控制多个线程对共享资源进行访问的工具,是并发编程的领域。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock (可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

package com.rgf;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        TestLock2 testLock3 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }

}

class TestLock2 implements Runnable {
    int ticketNums = 10;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            }
            finally{
                    //解锁
                    lock.unlock();
                    //如果同步代码有异常,要将unlock()写入finally语句块
                }
            }
        }
    }


运行界面如下所示:

示例二:

package com.ypl.ticket;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全问题的方式三:Lock锁---JDK5.0新增
 */

class  Window implements Runnable{
    private  int ticket=100;
    //1.实例化ReentrantLock
    private ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                //2.调用锁定方法:lock()
                lock.lock();

                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }finally {
                //3.调用解锁方法:unlock().
                lock.unlock();
            }

        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

 运行之后如下所示:

 synchronized与Lock的对比

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外) 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直再追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值