多线程Synchronized使用注意事项,死锁原因及如何诊断

本文深入探讨Java并发编程中常见的陷阱,包括空对象锁、synchronized作用域过大、不同monitor锁同一方法、多锁交叉死锁等问题,通过实例解析如何避免这些陷阱。

使用synchronized需要注意的问题

1.与monitor关联的对象不能为空

private fianl Object mutex = null;
public void syncMethod(){
	synchronized(mutex){
		//
	}
}

2.synchronized作用域太大
由于synchronized关键字在排他性,也就是说所有的线程必须串行地经过synchronized保护的共享区域,如果synchronized作用域越大,则代表着其效率低,甚至还会丧失并发的优势。
3.不同的monitor企图锁相同的方法

public static class Task implements Runnable{
	private final Object MUTEX = new Object();
	@Override
	public void run(){
		//....
		synchronized(MUTEX){
			//..
		}
		//..
	}
	public static void main(String[] args){
		for(int i = 0;i < 5; i++){
			new Thread(Task :: new).start();
		}
	}
}

上面的代码构造了五个线程,同时也构造了五个Runnable实例,Runnable作为线程逻辑执行单元传递给Thread,然后你将会发现,synchronized根本互斥不了与之对应的作用域,线程之间进行monitor lock 的争抢只能发生在与monitor关联的同一个引用上,上面的代码每一个线程争抢的monitor关联引用都是彼此独立的,因此不可能起到互斥的作用。
4.多个锁的交叉导致死锁
多个锁的交叉容易引起线程出现死锁的情况,程序并没有任何错误输出,但就是不工作,如下面的代码所示,

private final Object MUTEX_READ = new Object();
private final Object MUTEX_WRITE = new Object();
public void read(){
	synchronized(MUTEX_READ){
		synchronized(MUTEX_WRITE){
			//......
		}
	}
}
public void write(){
	synchronized(MUTEX_WRITE){
		synchronized(MUTEX_READ){
			//......
		}
	}
}

程序死锁

1.交叉锁可导致程序出现死锁
线程A持有R1的锁等待获取R2的锁,线程B持有R2的锁等待获取R1的锁(典型的哲学家吃面),这种情况最容易导致程序死锁。
2.内存不足
当并发请求系统可用内存时,如果此时系统内存不足,则可能会出现死锁的情况,举个例子,两个线程T1和T2,执行某个任务,其中T1已经获取了10MB内存,T2获取了20MB内存,如果每个线程的执行单元都需要30MB的内存,但是剩余可用的内存刚好为20M,那么两个线程有可能都在等待彼此能够释放内存资源。
3.一问一答式的数据交换
服务端开启某个端口,等待客户端访问,客户端发送请求立即等待接收,由于某种原因服务端错过了客户端的请求,仍然在等待一问一答式的数据交换,此时服务端和客户端都在等待着双方发送数据
4.数据库锁
无论是数据库级别的锁,还是行级别的锁,比如某个线程执行for update 语句退出了事物,其它线程访问该数据库时都将陷入死锁。
5.文件锁
某个线程获得了文件锁意外退出,其他读取该文件的线程也将会进入死锁直到系统释放文件句柄资源。
6.死循环引起的死锁
程序由于代码原因或者对某些异常处理不得当,进入死循环,虽然查看线程堆信息不会发现任何死锁的迹象,但是程序不工作,CPU占有率又居高不下,这种死锁一般称为系统假死,是一种最为致命也是最难排查的死锁现象,由于重现困难,进程对系统资源的使用量又达到了极限,想要做出dump有时候也是非常困难的。

程序死锁举例

程序由于交叉锁引起的死锁情况,代码如下:

public class DeadLock {
    private final Object MUTEX_READ = new Object();
    private final Object MUTEX_WRITE = new Object();
    public void read(){
        synchronized (MUTEX_READ){
            System.out.println(Thread.currentThread().getName() + " get MUTEX_READ");
            synchronized (MUTEX_WRITE){
                System.out.println(Thread.currentThread().getName() + " get MUTEX_WRITE");
            }
            System.out.println(Thread.currentThread().getName() + " release MUTEX_WRITE");
        }
        System.out.println(Thread.currentThread().getName() + " release MUTEX_READ");
    }
    public void write(){
        synchronized (MUTEX_WRITE){
            System.out.println(Thread.currentThread().getName() + " get MUTEX_WRITE");
            synchronized (MUTEX_READ){
                System.out.println(Thread.currentThread().getName() + " get MUTEX_READ");
            }
            System.out.println(Thread.currentThread().getName() + " release MUTEX_READ");
        }
        System.out.println(Thread.currentThread().getName() + " release MUTEX_WRITE");
    }

    public static void main(String[] args) {
        final DeadLock deadLock = new DeadLock();
        new Thread(() ->{
            while (true){
                deadLock.read();
            }
        },"READ-THREAD").start();

        new Thread(() ->{
            while (true){
                deadLock.write();
            }
        },"WRITE-THREAD").start();
    }

死锁诊断

1. 交叉锁引起的死锁
运行DeadLod代码,程序将陷入死锁,打开jstack工具或者jconsole工具,jstack -l PID会直接发现死锁的信息,在cmd下输入jsp,可以查看到DeadLock的进程id,使用jstack pid可以看到进程的状态,如图:在这里插入图片描述
一般交叉锁引起的死锁线程都会进入BLOCKED状态,CPU资源占用不高,很容易借助辅助工具来发现。
(2)死循环引起的死锁(假死)
运行如下程序(谨慎运行,可能会导致你的电脑死机,运行前将文件做好备份):

public class HashMapDeadLock {
    private final HashMap<String,String> map = new HashMap<>();
    public void add(String key,String value){
        this.map.put(key,value);
    }

    public static void main(String[] args) {
        final HashMapDeadLock hmd1 = new HashMapDeadLock();
        for(int x = 0;x < 2;x++){
            new Thread(() -> {
                for(int i = 1;i < Integer.MAX_VALUE;i++){
                    hmd1.add(String.valueOf(i),String.valueOf(i));
                }
            }).start();
        }
    }
}

运行HashMapDeadLock 程序,也可以使用jstack,jconsole,jvisualvm工具或者jProfiler(收费)工具进行诊断,但是不会给出很明显的提示,因为工作的线程并未BLOCKED,而是始终处于RUNNABLE状态,CPU使用率高居不下,甚至都不能正常运行你的命令。
严格意义上来说循环会导致程序假死,算不上真正的死锁,但是某个线程对CPU消耗过多,导致其他线程等待CPU,内存等资源也会陷入死锁等待。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值