Java多线程3(死锁)

死锁

源码来源:Java死锁及如何解决死锁
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象

一 死锁的产生有四个必要的条件

1.互斥使用,即当资源被一个线程占用时,别的线程不能使用
2.不可抢占,资源请求者不能强制从资源占有者手中抢夺资源,资源只能由占有者主动释放
3.请求和保持,当资源请求者在请求其他资源的同时保持对原因资源的占有
4.循环等待,多个线程存在环路的锁依赖关系而永远等待下去,例如T1占有T2的资源,T2占有T3的资源,T3占有T1的资源,这种情况可能会形成一个等待环路

这样记忆没有意义,所以换一种记忆的方式,锁的特点

加锁的资源只能同时被一个线程使用 -> 多个线程不能使用同一个资源
加锁的资源所在线程就算阻塞,也不会释放锁 -> 不释放已占有资源
加锁的资源所在线程不能强行释放锁 -> 资源未使⽤完之前,不能被强⾏剥夺
多个锁嵌套使用,容易造成循环等待资源,产生死锁

二 死锁的举例

死锁举例1

/**
 * @author Tony Stark
 * @create 2022-08-25 19:44
 * 死锁,th1拿到同步监视器o1,就是锁o1,接着需要拿到锁o2;
 *      同时,th2需要拿到o2,再拿到o1。两个线程会相互僵持,形成死锁
 *
 * 下面代码有一定概率不形成死锁,某一个线程迅速连续抢到时间片然后执行完毕,将锁释放给另一个线程
 * 所以,synchornized谨慎嵌套使用。
 */

class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run(){
        synchronized (o1){
            System.out.println("线程1拿到了o1锁,准备拿o2");
            synchronized (o2){
                System.out.println("线程1拿到了o2,结束");
            }
        }
    }
}
class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run(){
        synchronized (o2){
            System.out.println("线程2拿到了o2锁,准备拿o1");
            synchronized (o1){
                System.out.println("线程2拿到了o1锁,结束");
            }
        }
    }
}
public class DeadLock {
    public static void main(String[] args){
        Object o1 = new Object();
        Object o2 = new Object();

        MyThread1 th1 = new MyThread1(o1,o2);
        MyThread2 th2 = new MyThread2(o1,o2);

        th1.start();
        th2.start();

    }
}

死锁举例2(区分主线程和线程)

public class NormalDeadLock {
    //定义两个对象锁
    private static Object valueFirst = new Object();//第一个锁
    private static Object valueSecond = new Object();//第二个锁
    //先拿第一个锁,再拿第二个锁
    private static void fisrtToSecond() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        synchronized (valueFirst) {
            System.out.println(threadName+" get first");
            Thread.sleep(100);
            synchronized (valueSecond) {
                System.out.println(threadName+" get second");
            }
        }
    }
    //先拿第二个锁,再拿第一个锁
    private static void SecondToFisrt() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        synchronized (valueSecond) {
            System.out.println(threadName+" get second");
            Thread.sleep(101);
            synchronized (valueFirst) {
                System.out.println(threadName+" get first");
            }
        }
    }
    //执行先拿第二个锁,再拿第一个锁
    private static class TestThread extends Thread{
        private String name;
        public TestThread(String name) {
            this.name = name;
        }
        public void run(){
            Thread.currentThread().setName(name);
            try {
                SecondToFisrt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
    public static void main(String[] args) {
        Thread.currentThread().setName("TestDeadLock");
        TestThread testThread = new TestThread("SubTestThread");
        testThread.start();
        try {
            fisrtToSecond();//先拿第一个锁,再拿第二个锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

死锁举例3,动态死锁,经典的转账案例

/**
 *类说明:银行转账动作接口
 */
public interface ITransfer {
	/**@param from 转出账户
	 * @param to 转入账户
	 * @param amount 转账金额
	 * @throws InterruptedException
	 */
    void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException;
}
/**
 *类说明:用户账户的实体类
 */
public class UserAccount {
    private final String name;//账户名称
    private int money;//账户余额
    //显示锁
    private final Lock lock = new ReentrantLock();
    public Lock getLock() {
        return lock;
    }
    public UserAccount(String name, int amount) {
        this.name = name;
        this.money = amount;
    }
    public String getName() {
        return name;
    }
    public int getAmount() {
        return money;
    }
    @Override
    public String toString() {
        return "UserAccount{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
    //转入资金
    public void addMoney(int amount){
        money = money + amount;
    }
    //转出资金
    public void flyMoney(int amount){
        money = money - amount;
    }
}
/**
 *类说明:不安全的转账动作的实现
 *解释:from to都是用户UserAccount类,假设有账户F1,F2,F3,T1,T2,T3
 *     比如线程1(F1,T1,100),线程2(F1,T2,200),先锁住from,两个线程同时进来没问题。因为都是从F1转出。在锁to,保证线程安全。
 */
public class TrasnferAccount implements ITransfer {
	
    @Override
    public void transfer(UserAccount from, UserAccount to, int amount) 
    		throws InterruptedException {
        synchronized (from){//先锁转出
            System.out.println(Thread.currentThread().getName()
            		+" get"+from.getName());
            Thread.sleep(100);
            synchronized (to){//再锁转入
                System.out.println(Thread.currentThread().getName()
                		+" get"+to.getName());
                from.flyMoney(amount);
                to.addMoney(amount);
            }
        }
    }
}
/**
 *类说明:模拟支付公司转账的动作
 */
public class PayCompany {
 
	/*执行转账动作的线程*/
    private static class TransferThread extends Thread{
        private String name;//线程名字
        private UserAccount from; 
        private UserAccount to; 
        private int amount;
        private ITransfer transfer; //实际的转账动作,采用多态,父类引用指向子类对象
      
        public TransferThread(String name, UserAccount from, UserAccount to,
                              int amount, ITransfer transfer) {
            this.name = name;
            this.from = from;
            this.to = to;
            this.amount = amount;
            this.transfer = transfer;
        }
        public void run(){
            Thread.currentThread().setName(name);
            try {
                transfer.transfer(from,to,amount);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        PayCompany payCompany = new PayCompany();
        UserAccount zhangsan = new UserAccount("zhangsan",20000);
        UserAccount lisi = new UserAccount("lisi",20000);
        ITransfer transfer = new TransferThread();//多态
        TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi"
                ,zhangsan,lisi,2000,transfer);
        TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan"
                ,lisi,zhangsan,4000,transfer);
        zhangsanToLisi.start();
        lisiToZhangsan.start();
    }
}

本例使用了接口,多态。父类引用指向子类对象
输出

zhangsanToLisi get zhangsan
lisiToZhangsan get lisi

这是一个相对比较典型的动态锁,虽然在TrasnferAccount.transfer方法中规定了加锁的顺序,但这个加锁不能确保调用输入from、to两个参数的顺序,导致形成死锁。
如果执行如下代码便不会出现死锁

TransferThread zhangsan1ToLisi1 = new TransferThread("zhangsan1ToLisi1"
                ,zhangsan1,lisi1,2000,transfer);
TransferThread zhangsan1ToLisi2 = new TransferThread("zhangsan1ToLisi2"
                ,zhangsan1,lisi2,2000,transfer);

zhangsan1ToLisi1.start();
zhangsan1ToLisi2.start();

三 死锁的解决方法

1.尝试取锁tryLock()

tryLock()方法是有返回值的,返回值是Boolean类型。它表示的是用来尝试获取锁:成功获取则返回true;获取失败则返回false
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

保证每个线程先顺利取到from,在取到to即可,如果没有就不断尝试去抢夺。
举例:
th1(F1,T1,2000)
th2(T1,F1,1000)
th1先抢到F1→还没抢到T1,th2就抢到了T1→th1无法进行,释放F1→th2抢到F1,执行命令→th2释放F1 释放T1(这是一种可能的情况)

public class SafeOperateToo implements ITransfer {
    @Override
    public void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException {
    	Random r = new Random();
    	while(true) {//不断进行
    		if(from.getLock().tryLock()) {
    			try {
    				System.out.println(Thread.currentThread().getName() + " get "+from.getName());
    				if(to.getLock().tryLock()) {
    					try {
    	    				System.out.println(Thread.currentThread().getName() + " get "+to.getName());    						
    						//两把锁都拿到了
    	                    from.flyMoney(amount);
    	                    to.addMoney(amount);
    	                    break;
    					}finally {
    						to.getLock().unlock();//最总要释放锁to
    					}
    				}
    			}finally {
    				from.getLock().unlock();//无论有没有执行锁to代码,也要释放锁from
    			}
    		}
    		Thread.sleep(r.nextInt(10)); //线程睡眠小技巧,加快取锁
    	}
    }
}

2.使用System.identityHashCode(obj)解决动态死锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值