同步:在高并发的情况下,为了防止数据出错,一个线程对于共享资源执行操作的时候,另外的线程要执行操作此共享资源需要等待前一个线程释放此共享资源,才能操作。
同步监视器:共享资源。
同步函数:synchronized修饰的方法,同步监视器为当前this对象。
同步代码块:synchronized修饰的代码块,同步监视器可以自定义。
同步锁Lock:另一种更加强大的线程安全机制:通过显示的定义同步锁对象来实现同步,同步锁对象由Lock对象充当。
两者同步机制的区别:Lock是显示的使用Lock对象作为同步监视器,同步方法隐式的使用当前对象作为同步监视器。
Lock开启后必须关闭:lock()//加锁 unlock()//释放锁
死锁:简单来说就是两个没有释放的同步进程都想取得对方共享资源(同步监视器)的操作权。
举个例子:有同步函数A,B 有资源a1,b1 问题:A,B都想取得a1,b1的操作权。
假设:A先取的a1的操作权,同时B取得了b1的操作权,此时,A再想取得b1得操作权必须等同步函数B先释放资源,但是B由于没有拿到a1得操作权所以一直没有释放资源。此时就会造成线程得等待死锁(阻塞死锁)
死锁分类:
1.等待死锁(如上)。
2.递归死锁:(一般很少人在递归函数里面加锁,很容易死翘翘)
举例:A同步函数,B非同步函数。A同步函数里调用了B非同步函数,但是B非同步进程里又调用了A同步进程。
假设:运行A同步函数时,由于A中同步函数调用了B函数,所以进入B函数,B函数再次调用了A同步函数,因为之前得A同步函数资源还没有释放,再从B函数中调用A同步函数会造成,再次调用得A同步函数进入递归死锁状态。
避免死锁得技术:
1.加锁顺序
所有线程按照一定顺序获取锁,避免按照不同顺序加锁时出现死锁。
举例:
有线程A&B&C,有锁:lock1&lock2&lock3
A{lock1,lock2,lock3}
B{lock2,lock3}
C{lock1}
A想获得lock3,必须先获得lock1,但是lock1被C加锁。所以A需要等待C释放即可,
A获得lock1,继续获得lock2,想获得lock2必须等待B释放资源。等待B释放资源,A终于获得lock3。
反例:
有线程A&B&C,有锁:lock1&lock2&lock3
A{lock1,lock2,lock3}
B{lock3,lock1}
C{lock2}
A想获得lock3, B想获得lock1,A获得了lock1,开始获得lock2,等待C释放资源,
此时B获得lock3,需要获得lock1。C释放资源后,A获得lock2,但是此时lock3被B加锁,但是lock1又被A加锁。此时A,B就发生了死锁。
缺点:需要知道所有加锁情况,全局分析(难以避免未知情况)
2.加锁时限
在获取锁的过程中加上超时时间,超时则释放资源并回退,稍后再尝试加锁。
举例:
如1中的反例:假设A线程设置了300毫秒的限时,B线程设置了100毫秒的限时,
如果B等待时间超过限时,释放资源并回退,此时A就可以成功获得所有锁并结束程序。然后B再尝试获取锁,就可以解决死锁问题。
缺点:当线程增至10条或20条以上,各线程的限时时长就越发接近,那么越容易造成死锁。例如:如果上例子中A,B线程限时时常一样,那么死锁依然会发生。
3.死锁检测
死锁检测机制,主要针对顺序加锁和加锁限时都无法实现的情况下使用。
原理:每当一个线程获得或者请求锁后,获得或请求的锁信息会在锁和线程的数据结构中记录下来。当一个线程请求锁失败时,会遍历锁的关系图,查看是否发生死锁。
举例:
简单例子:
有线程A,B, 有锁lock1,lock2
A{lock1,lock2}
B{lock2,lock1}
此时当A请求lock2失败时,会检测自身是否加锁了B请求的锁。如果有就发生了死锁。
复杂例子:
多线程的递归检测死锁:
有线程A,B,C,D 有锁lock1,lock2,lock3,lock4
A{lock1,lock2}
B{lock2,lock3}
C{lock3,lock4}
D{lock4,lock1}
A加锁lock1,加锁lock2时请求失败,检测锁的关系图,发现lock2被B加锁,
B请求的lock3,又被C加锁,C请求的lock4又被D加锁,D请求的lock1又被
A加锁。造成递归加锁。
当检测到死锁后:线程回退,等待一段时间再重新尝试加锁。