多线程的死锁案例
1.什么是多线程死锁
同步中嵌套同步,导致锁无法释放
本质的详细描述为:
所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些线程都将无法向
前推进。 所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。
2.模拟一个死锁的例子
示例代码
package com.rj.bd.dxc.aq02;
/**
* @desc 线程死锁
* @time 2020-10-27
*/
public class MyThread implements Runnable
{
private static Object obj1=new Object();
private static Object obj2=new Object();
private boolean flag;
public MyThread(boolean flag) {
super();
this.flag = flag;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"在运行");
if (flag) {
synchronized (obj1) {
System.out.println(Thread.currentThread().getName()+"已经锁定obj1");
try {
obj1.wait(800);
//Thread.sleep(1000);
}catch (InterruptedException e) {
System.out.println("中断异常....");
} synchronized (obj2) {
System.out.println("1秒种之后"+Thread.currentThread().getName()+"锁定obj2");
}
}
}else{
synchronized (obj2) {
System.out.println(Thread.currentThread().getName()+"已经锁定obj2");
try {
obj2.wait(1000);
//Thread.sleep(1000);
}catch (InterruptedException e) {
System.out.println("中断异常....");
} synchronized (obj1) {
System.out.println("1秒种之后"+Thread.currentThread().getName()+"锁定obj1");
}
}
}
}
}
3.如何避免死锁
1)加锁顺序(线程按照一定的顺序加锁):就是你的run()方法里面怎样写的逻辑你怎样去设定你加锁的顺序
2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
3)死锁检测:所谓的检查就是通过相应的工具逐个线程的去检测
3.1)通过加锁顺序避免死锁:
将Test.java中,Thread t2 = new Thread(new Mythread(false), “线程2”);
改为:Thread t2 = new Thread(new Mythread(true), “线程2”);
现在应该不会出现死锁了,因为线程1和线程2都是先对obj1加锁,然后再对obj2加锁,当t1启动后,锁住了obj1,而t2也启动后,只有当t1释放了obj1后t2才会执行,从而有效的避免了死锁。
3.2)通过加锁时限避免死锁:
package com.rj.bd.threads.thread13;
/**
* @desc 模拟一个死锁的多线程
* @time 2019-10-19
*/
public class Mythread implements Runnable{
private static Object obj1 = new Object();//创建两个锁对象,分别用来一会去锁两个子线程
private static Object obj2 = new Object();
private boolean flag;
public Mythread(boolean flag){
this.flag = flag;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "运行");
if(flag)
{
synchronized(obj1)//t1先运行,这个时候flag==true,先锁定obj1,然后睡眠1秒钟
{
System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
try
{
obj1.wait(800);
// Thread.sleep(1000); //此时让t1在睡眠的时候,另一个线程t2启动,flag==false,先锁定obj2,然后也睡眠1秒钟
}
catch (InterruptedException e)
{
System.out.println("中断异常.....");
}
synchronized(obj2)
{
//t1睡眠结束后需要锁定obj2才能继续执行,而此时obj2已被t2锁定(此处代码不会被执行的)
System.out.println("1秒钟后,"+Thread.currentThread().getName()+ "锁住obj2");
}
}
}
else
{
synchronized(obj2)
{
System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
try
{ obj2.wait(1000);
// Thread.sleep(1000);
}
catch (InterruptedException e)
{
System.out.println("中断异常.....");
}
synchronized(obj1)
{
//t2睡眠结束后需要锁定obj1才能继续执行,而此时obj1已被t1锁定(此处代码不会被执行的)
System.out.println("1秒钟后,"+Thread.currentThread().getName()+ "锁住obj1");
}
}
}
}
}
显示锁lock
1.什么是显示锁:
lock,在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock接口
提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁
简而言之:就是JDK中新实现的一个功能类似synchroized的类
2.Lock写法:
Lock lock = new ReentrantLock();
lock.lock();//加锁
try
{
//可能会出现线程安全的操作
}
finally
{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
lock.unlock();//释放锁
}
Tickets.java
package com.rj.bd.thredas.thread07;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @desc 模拟电影院售票(显示锁:Lock)
* @time 2019-10-20
*/
public class Tickets implements Runnable {
public int tickets = 100;
// Object lock=new Object();//创建了一个对象锁
Lock lock=new ReentrantLock();
/**
* 补充: 通过跟踪JVM我们知道java中类的实例化对象在运行的过程中会加上一个锁,
* 这个锁就叫做对象锁/内置锁
* 当应用在同步代码块的时候其充当互斥锁的效果
*/
@Override
public void run()
{
while (true)
{
try
{
Thread.sleep(300);
lock.lock();
synMethod();
}
catch (Exception e)
{
System.out.println("中断异常.....");
}
finally
{
//用完锁之后要手动的释放(如果不手动的释放锁,
//那么就会出现只有一个线程抢到了资源,那么就是这个线程一直执行)
lock.unlock();
}
}
}
/**
* @desc 同步方法
* @throws InterruptedException
*/
private void synMethod() throws InterruptedException {
if (tickets > 0)
{
Thread.sleep(40);
System.out.println(Thread.currentThread().getName() +
" 正在出售第 " + tickets-- + " 张票");
}
else
{
System.out.println("票已经售完了");
System.exit(0);
}
}
}
Test.java
package com.rj.bd.thredas.thread07;
/**
* @desc 测试类:测试
* @time 2019-10-20
*/
public class Test {
public static void main(String[] args) {
Tickets tickets=new Tickets();
Thread thread01 = new Thread(tickets);
Thread thread02 = new Thread(tickets);
Thread thread03 = new Thread(tickets);
Thread thread04 = new Thread(tickets);
Thread thread05 = new Thread(tickets);
Thread thread06 = new Thread(tickets);
thread01.setName("窗口1");
thread02.setName("窗口2");
thread03.setName("窗口3");
thread04.setName("窗口4");
thread05.setName("窗口5");
thread06.setName("窗口6");
thread01.start();
thread01=null;
thread02.start();
thread02=null;
thread03.start();
thread03=null;
thread04.start();
thread04=null;
thread05.start();
thread05=null;
thread06.start();
}
}
3.Lock和synchronized的区别(拓展)
Lock和synchronized的区别
3.1). 是否可以设置超时时间:
Lock获取锁时是可以设置超时时间的。而Synchronized获取锁时是没有超时时间的,它会一直在那里等着
PS: lock.tryLock(1, TimeUnit.SECONDS);
3.2). 是否可以被中断:
Lock获取锁可以被中断,synchronized不可以被中断
PS:Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,
当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
我么可以通过使用: // Thread.sleep(300);//把这行代码注释掉不然效果出不来
lock.tryLock(1, TimeUnit.SECONDS);
3.3).释放锁方式:
使用Lock,必须手动释放锁。synchronized在操作系统层面实现释放锁
3.4).是否阻塞:
synchronized是阻塞式的(一个线程获取锁之后,其他锁只能等待那个线程释放之后才能有获取锁
的机会);
tryLock是非阻塞的(它表示的是用来尝试获取锁:成功获取则返回true;获取失败则返回false,
这个方法无论如何都会立即返回)
3.5).可重入性:
synchronized是可重入锁(可重入:一个线程多次去获取一把锁)。因为synchronized内部通过
一个计数器来记录获取锁的个数,每获取一把锁,计数器加1.每释放一把锁,计数器就减一。
ReentrantLock也是可重入锁
PS:重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不
发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁
3.6).是否公平:
如果在时间上,先获取锁的请求,一定先被满足,这个锁就是公平的。
非公平锁的效率一般来说更高。(在公平的情况下,拿不到锁的线程会进行排队并被挂起,如果轮到该线
程去获取锁,该线程还需要解除挂起,然后才能获取锁。因为解除挂起这个操作比较费时,导致了公平锁
的效率不高。非公平锁不用排队)
挂起:可以理解为操作系统把当前线程从内存中移除
ReentrantLock的构造方法可以指定是公平锁还是非公平锁,默认非公平锁
3.7).是否是排他锁:
排他锁:同一时刻只能允许一个线程访问
synchronized和ReentrantLock都是排他锁
停止线程(特例补充)
1.如何停止(终止,中止,结束)线程:
1) 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2) 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,
也可能发生不可预料的结果),类比理解为机器正在运转突然拔电源了(不推荐的)
3)使用interrupt( )方法中断线程
1.1)使用退出标志,使线程正常退出
package com.rj.bd.threads.thread17;
/**
* @desc 通过标识位终止线程
* @time 2019-10-21下午05:29:31
*/
public class Test {
private static volatile boolean stop=false;
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(new Runnable() {
@Override
public void run()
{
while (!stop)
{
System.out.println(Thread.currentThread().getName()+"在运行。。。。");
}
}
},"子线程");
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);
stop=true;
/**
* 定义一个用volatile修饰的成员变量来控制线程的停止,
* 这点是利用了volatile修饰的成员变量可以在多线程之间达到共享,也就是可见性来实现的
*/
Thread.sleep(10);//让主线程睡眠10毫秒,目的是能够演示出子线程的状态已经是终止的
System.out.println("子线程的状态:"+thread.getState());
}
}
1.2)使用interrupt( )方法中断线程:
package com.rj.bd.threads.thread17;
/**
* @desc 通过interrupt中断线程
* @time 2019-10-21下午05:38:19
*/
public class StopThread extends Thread {
public void run()
{
try
{
sleep(50000); // 延迟50秒
}
catch (InterruptedException e)
{
System.out.println("中断异常.....");
}
}
}
package com.rj.bd.threads.thread17;
import java.io.IOException;
/**
* @desc 使用interrupt方法中断线程
* @author WYH
* @time 2019-10-21下午05:35:56
*/
public class Test02 {
public static void main(String[] args) throws IOException, InterruptedException {
System.out.println("主线程开始.....");
StopThread thread = new StopThread();
thread.start();
System.out.println("在50秒之内按任意键中断线程!");
System.in.read();//读取键盘中按下的任意键
thread.interrupt();
thread.join();
System.out.println("子线程:"+thread.getState());
System.out.println("主线程结束......");
}
}