死锁,顾名思义就是由于加锁所导致的死循环(也就是程序由于加锁操作而僵住了)
public static void main(String[] args) throws InterruptedException {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(()->{
synchronized (locker1)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker2)
{
System.out.println(Thread.currentThread().getName()+"获取了两把锁");
}
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (locker2)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1)
{
System.out.println(Thread.currentThread().getName()+"获取了两把锁");
}
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
}
最经典的哲学家吃面问题:
这个例子,哲学家相当于线程,然后筷子相当于锁,由于哲学家互相抢对方的筷子,所以没人能够吃面
JVM在对对象加锁之前,会先验证当前对象是否已经被加锁,如果别加锁就不会进行二次加锁,这样就有效避免产生死锁~
死锁产生的四个必备条件(缺一不可):
1、锁是不可抢占的(一但被持有,其他的线程不能够再次进行获取,要么等待锁释放,要么放弃获取)
2、锁是互斥的(即锁和拿锁的对象是一对一,多个对象获取锁需要阻塞等待)
3、在已经获取一把锁的情况下,尝试获取另外一把锁,拿不到锁就阻塞等待
4、在获取锁的过程中,如果没拿到锁会产生循环等待(拿不到一直等)
那么怎么破坏死锁?
当我们在对自己的代码块进行加锁时,对于锁的模拟实现,我们可以自行控制锁的特性(自行实现,例如规定锁可以给多个线程持有,锁不具备互斥性等..)
①规定线程获取锁的顺序
②规定线程的阻塞等待方式(在遇到锁被占有的情况下,线程不硬等锁释放或者直接跳过阻塞等待)
综上所述,只要破坏四个必备的条件之一,就能避免产生死锁,而在平时使用锁的时候尽量避免写嵌套锁(嵌套锁在java中有JVM进行处理,会避免对象进行二次加锁,但是如果在c++中写嵌套锁那就会僵住),这样才能避免产生死锁~