Java多线程之synchronized及死锁编写

本文深入解析Java中的死锁概念,并通过代码示例展示如何编写一个简单的死锁情况。主要内容包括Java锁机制、对象锁与静态锁的区别、以及如何利用synchronized关键字避免死锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java中锁很常见,尤其是在多线程的情况下,我们会经常使用到锁。面试中我们也会经常被问到如何编写一个死锁。

java提供synchronized关键字来提供锁机制,在多线程中为了使程序并行我们会常使用到锁,synchronized就是其中最简单的实现方式,首先我们来看一下synchronized最基本的用法:

首先我们新建Person类,其中包含如下代码:

public class Person {
    public synchronized void sleep(String threadName)  {
        System.out.println("get sleep lock:" + threadName);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("sleep error:" + threadName);
        }
        System.out.println("sleep end:" + threadName);
    }
}

然后运行如下方法进行测试:

private static void testSingleMethod(){
    final Person p1 = new Person();
    Thread sleepThread = new Thread(() -> p1.sleep("sleepThread"));
    Thread sleepThreadExt = new Thread(() -> p1.sleep("sleepThreadExt"));
    sleepThread.start();
    sleepThreadExt.start();
}

执行结果如下:

get sleep lock:sleepThread
sleep end:sleepThread
get sleep lock:sleepThreadExt
sleep end:sleepThreadExt

上面就是synchronized关键字的基本用法,可以看到线程1释放对象锁后线程二才能获取对象锁继而执行sleep方法,所以可以保证线程的安全。下面我们再来看个例子:
Person类代码如下:

public synchronized void sleep(String threadName)  {
    System.out.println("get sleep lock:" + threadName);
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        System.out.println("sleep error:" + threadName);
    }
    System.out.println("sleep end:" + threadName);
}

public synchronized void eat(String threadName)  {
    System.out.println("get eat lock:" + threadName);
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        System.out.println("eat error:" + threadName);
    }
    System.out.println("eat end:" + threadName);
}

然后运行如下方法进行测试:

private static void testMutipleMethod(){
    final Person p1 = new Person();
    Thread sleepThread = new Thread(() -> p1.sleep("sleepThread"));
    Thread eatThread = new Thread(() -> p1.eat("eatThread"));
    sleepThread.start();
    eatThread.start();
}

执行结果如下:

get sleep lock:sleepThread
sleep end:sleepThread
get eat lock:eatThread
eat end:eatThread

通过上面的例子我们可以看出,synchronized加在方法前其实不是锁的对象中的某个方法,而是锁的对象,同样我们不修改Person类,只是修改执行时的方法再看下:

private static void testMutipleMethodAndMutiple(){
    final Person p1 = new Person();
    final Person p2 = new Person();
    Thread sleepThread = new Thread(() -> p1.sleep("sleepThread"));
    Thread eatThread = new Thread(() -> p2.eat("eatThread"));
    sleepThread.start();
    eatThread.start();
}

执行结果如下:

get sleep lock:sleepThread
get eat lock:eatThread
sleep end:sleepThread
eat end:eatThread

可以发现,synchronized加在方法名前时我们的p1,p2对象的锁是互不干扰的,只跟具体对象有关,这就是对象锁的意思。
接下来我们看下如下代码:
Person类代码如下:

public class Person {
    public static synchronized void sleep(String threadName)  {
        System.out.println("get sleep lock:" + threadName);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("sleep error:" + threadName);
        }
        System.out.println("sleep end:" + threadName);
    }

    public static synchronized void eat(String threadName)  {
        System.out.println("get eat lock:" + threadName);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("eat error:" + threadName);
        }
        System.out.println("eat end:" + threadName);
    }
}

可以看到此处只是在方法名前面加了static关键字,再来看下上面的testMutipleMethodAndMutiple()方法的执行结果:

get sleep lock:sleepThread
sleep end:sleepThread
get eat lock:eatThread
eat end:eatThread

大家是否发现此时依旧是p1,p2两个对象,但是线程二还是在线程一的锁对象释放以后才获取到Person类的锁,此处加了static关键字后synchronized锁的是Person类,而不是具体对象了,我们将Person代码改写为如下,大家将会更清晰的看明白:

public class Person {
    public void sleep(String threadName)  {
        synchronized(Person.class) {
            System.out.println("get sleep lock:" + threadName);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("sleep error:" + threadName);
            }
            System.out.println("sleep end:" + threadName);
        }
    }

    public void eat(String threadName)  {
        synchronized(Person.class) {
            System.out.println("get eat lock:" + threadName);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("eat error:" + threadName);
            }
            System.out.println("eat end:" + threadName);
        }
    }
}

修改Person后大家再执行上面的调用方法,可以发现执行结果一致,上面static方法中加synchronized关键字其实就等同于此处的synchronized(Person.class),都是锁的对象,大家可按需选择。同样的,之前的锁对象的方式可以将Person类的锁改为synchronized(this)即可,大家可以自己试一下。

接下来我们来讲如何编写一个最简单的死锁,死锁其实就是线程在请求一个锁时,该锁一直处于等待状态(如:那个锁已被别的线程获取,且不会释放),于是一直处于等待中状态,就造成了我们的死锁,死锁会直接导致我们线程池中此线程一直挂起,相当于线程池中可执行任务的线程少了一个,如果死锁越来越多,线程池中将没有可执行任务的线程,于是就导致了服务的宕机。

原理大家知道了,那么我们看如下具体代码的实现:

private static void testDeadLock() {
    final Person p1 = new Person();
    final Person p2 = new Person();
    Thread sleepThread = new Thread(() ->{
        synchronized (p1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sleepThread get p1's lock");
            synchronized (p2){
                System.out.println("sleepThread get p2's lock");
            }
            System.out.println("sleepThread release p2's lock");
        }
        System.out.println("sleepThread release p1's lock");
    });
    Thread sleepThreadExt = new Thread(() -> {
        synchronized (p2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sleepThreadExt get p2's lock");
            synchronized (p1){
                System.out.println("sleepThreadExt get p1's lock");
            }
            System.out.println("sleepThreadExt release p1's lock");
        }
        System.out.println("sleepThreadExt release p2's lock");
    });
    sleepThread.start();
    sleepThreadExt.start();
}

执行结果如下:

sleepThread get p1's lock
sleepThreadExt get p2's lock

执行程序可以看出,控制台只打印了如上两句,之后就一直处于挂起状态。这是因为线程sleepThread获取到p1对象的锁之后去请求p2对象的锁,而这时p2对象的锁已经被线程sleepThreadExt获取了,所以sleepThread只能等待sleepThreadExt释放p2对象锁后才能获取,之后才能继续执行代码,但是sleepThreadExt同样是在等待sleepThread释放p1对象的锁,以便执行后续代码,于是两个线程间就出现了死锁,两个线程就会一直处于等待获取锁的状态。这就是死锁的原理。

欢迎关注个人公众号:读书健身编程
我的博客:blog.scarlettbai.com


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值