博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程之synchronized及死锁编写
阅读量:4079 次
发布时间:2019-05-25

本文共 6239 字,大约阅读时间需要 20 分钟。

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:sleepThreadsleep end:sleepThreadget sleep lock:sleepThreadExtsleep 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:sleepThreadsleep end:sleepThreadget eat lock:eatThreadeat 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:sleepThreadget eat lock:eatThreadsleep end:sleepThreadeat 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:sleepThreadsleep end:sleepThreadget eat lock:eatThreadeat 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 locksleepThreadExt get p2's lock

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

欢迎关注个人公众号:读书健身编程

我的博客:blog.scarlettbai.com


你可能感兴趣的文章
路由选择算法
查看>>
Objective-C 基础入门(一)
查看>>
Objective-C 基础入门(三) 读写文件与回调
查看>>
C++ STL标准库与泛型编程(一)概述
查看>>
C++ STL标准库与泛型编程(四)Deque、Queue、Stack 深度探索
查看>>
C++ STL标准库 算法
查看>>
JVM内存模型_Minor GC笔记
查看>>
SpringCloud学习之PassCloud——(一)PassCloud源代码下载
查看>>
Linux下安装Python环境并部署NLP项目
查看>>
Nginx篇-springCloud配置Gateway+Nginx进行反向代理和负载均衡
查看>>
Nginx篇-Nginx配置动静分离
查看>>
缓存篇-Redis缓存失效以及解决方案
查看>>
缓存篇-使用Redis进行分布式锁应用
查看>>
缓存篇-Redisson的使用
查看>>
phpquery抓取网站内容简单介绍
查看>>
找工作准备的方向(4月22日写的)
查看>>
关于fwrite写入文件后打开查看是乱码的问题
查看>>
用结构体指针前必须要用malloc,不然会出现段错误
查看>>
Linux系统中的美
查看>>
一些实战项目(linux应用层编程,多线程编程,网络编程)
查看>>