多线程死锁

多线程的死锁案例

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("主线程结束......");
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值