本文共 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