Java

线程之活跃度失败(死锁、活锁、饥饿)

2019-04-29  本文已影响5人  安仔夏天勤奋

线程活跃度

活跃度问题是指线程或进程长时间得不到cpu占用。《Java并发编程实战》中提到,无论执行计算密集操作还是执行某个可能阻塞的操作,如果持有锁的时间过长,都会带来活跃性或性能问题。

活跃度失败有那几种

死锁

多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

死锁产生的原因

死锁产生的必要四个条件

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。

写一个死锁的例子

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread1 implements Runnable {
    @Override
    public void run() {
        ////模拟线程1占用资源1并申请获得资源2的锁
        try {
            System.out.println("LockThread1 is running");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待线程2锁定资源2
                synchronized (ThreadResource.resource2){
                    System.out.println("LockThread1 lock resource2");
                }
                System.out.println("LockThread1 release resource2");
            }
            System.out.println("LockThread1 release resource1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread1 is stop");
    }
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread2 implements Runnable {
    @Override
    public void run() {
        //模拟线程2占用资源2并申请获得资源1的锁:
        try {
            System.out.println("LockThread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待线程1锁定资源1
                synchronized (ThreadResource.resource1) {
                    System.out.println("LockThread2 lock resource1");
                }
                System.out.println("LockThread2 release resource1");
            }
            System.out.println("LockThread2 release resource2");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread2 is stop");
    }
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class ThreadResource {
    public static Object resource1 = new Object();
    public static Object resource2 = new Object();
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class DeadLock{
    public static void main(String[] args) {
        new Thread(new LockThread1()).start();
        new Thread(new LockThread2()).start();
    }
}

运行结果

Thread1 is running
Thread2 is running
Thread1 lock resource1
Thread2 lock resource2
​
并且程序一直无法结束。这就是由于线程1占用了资源1,此时线程2已经占用资源2,这个时候线程1想要使用资源2,线程2想要使用资源1。两个线程都无法让步,导致程序死锁。

如何避免死锁

死锁是可以避免的。用于避免死锁的技术三种方式:

由上面的例子可以看出当线程在同步某个对象里,再去锁定另外一个对象的话,就和容易发生死锁的情况。最好是线程每次只锁定一个对象并且在锁定该对象的过程中不再去锁定其他的对象,这样就不会导致死锁了。比如将以上的线程改成下面这种写法就可以避免死锁

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread1 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread1 is running");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待线程2锁定资源2
            }
            System.out.println("LockThread1 release resource1");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread1 lock resource2");
            }
            System.out.println("LockThread1 release resource2");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread1 is stop");
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread2 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待线程1锁定资源1
            }
            System.out.println("LockThread2 release resource2");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread2 lock resource1");
            }
            System.out.println("LockThread2 release resource1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread2 is stop");
    }
}

运行结果

LockThread1 is running
LockThread1 lock resource1
LockThread2 is running
LockThread2 lock resource2
LockThread1 release resource1
LockThread1 lock resource2
LockThread1 release resource2
LockThread1 is stop
LockThread2 release resource2
LockThread2 lock resource1
LockThread2 release resource1
LockThread2 is stop

如果需要同时去锁定两个对象,可以根据加锁顺序定义一个先后的规则。按照上面的例子,需要同时锁定两个资源,可以根据资源的hashcode值大小来判断先后锁定顺序。代码如下:

public class LockThread3 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread3 is running");
            if ( ThreadResource.resource1.hashCode() > ThreadResource.resource2.hashCode() ) {
                //先锁定resource1
                synchronized (ThreadResource.resource1) {
                    System.out.println("LockThread3 lock resource1");
                    Thread.sleep(2000);
                    synchronized (ThreadResource.resource2) {
                        System.out.println("LockThread3 lock resource2");
                    }
                    System.out.println("LockThread3 release resource2");
                }
                System.out.println("LockThread3 release resource1");
            }else {
                //先锁定resource2
                synchronized (ThreadResource.resource2) {
                    System.out.println("LockThread3 lock resource2");
                    Thread.sleep(2000);
                    synchronized (ThreadResource.resource1) {
                        System.out.println("LockThread3 lock resource1");
                    }
                    System.out.println("LockThread3 release resource1");
                }
                System.out.println("LockThread3 release resource2");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread3 is stop");
    }
}

死锁更详细可参照

活锁

活锁是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

活锁不会被阻塞,而是不停检测一个永远不可能为真的条件。除去进程本身持有的资源外,活锁状态的进程会持续耗费宝贵的CPU时间。

举个例子,两个人在走廊上碰见,大家都互相很有礼貌,互相礼让,A从左到右,B也从从左转向右,发现又挡住了地方,继续转换方向,但又碰到了,反反复复,一直没有机会运行下去。

活锁例子

活锁的解决方法

活锁和死锁的区别

饥饿

饥饿是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求......,T2可能永远等待。

线程长时间无法获得共享资源从而继续相继的处理。这种情况经常发生在当共享资源被“贪婪”线程长时间占据时。假设一个对象提供的互斥方法需要很长时间处理才能返回,然而如果某线程老是频繁激活这个方法,那么其他需要访问该对象的线程就会被长时间阻塞,而处于饥饿状态。

饥饿.png

Java中的读写锁的实现类ReentranctReadWriteLock,在默认使用非公平模式(不是先来先处理的模式)的情况下,如果某个线程想要读取资源,只要没有线程正在对该资源进行写操作且没有线程请求对该资源的写操作即可。如果读操作发生的比较频繁,我们又没有提升写操作的优先级,那么就会产生“饥饿”现象。请求写操作的线程会一直阻塞,直到所有的读线程都从ReentranctReadWriteLock上解锁了。如果一直保证新线程的读操作权限,那么等待写操作的线程就会一直阻塞下去,结果就发生了“饥饿”。

java代码会引起这种类型的饥饿

synchronized(obj) {
 while (true) {
 // .... infinite loop
 }
}

优先级引起也会引起线程饥饿

高优先级线程吞噬所有的低优先级线程的CPU时间。例如在java中调用了Thread.setPriority方法设置了线程优先级,优先级低的线程始终得不到执行的机会,虽然线程优先级对于不同操作系统的实现方式不一样,即便设置了优先级也不一定会有效果,但还是有可能会出现这种情况。

饥饿的解决办法有

上一篇下一篇

猜你喜欢

热点阅读