03.活跃性问题

[TOC]

概念

活跃性是指某件正确的事情最终会发生,当某个操作无法继续下去的时候,就会发生活跃性问题。在多线程中一般有死锁、活锁和饥饿问题。

  • 死锁

多个线程因为环形的等待锁的关系而永远的阻塞下去。

  • 活锁

线程不断重复执行相同的操作,而且总会失败。当多个相互协作的线程都对彼此进行响应而修改各自的状态,并使得任何一个线程都无法继续执行(只能一直重复着响应和修改自身状态),就发生了活锁。如果迎面两个人走路互相让路,总是没有随机性地让到同一个方向,那么就会永远地避让下去。

  • 饥饿

当线程无法访问它所需要的资源而导致无法继续时,就发生了饥饿。如一个线程占有锁永远不释放,等待同一个锁的其他线程就会发生饥饿。

死锁

Java对待死锁的解决方法只要重启程序,所以避免产生死锁十分重要。
死锁主要是因为内嵌锁获取的情况,即占有一个锁并试图获取另一个锁,就很容易和其他的线程发生冲突。

原因

多个线程直接对锁等待的关系产生了环路,如A持有锁A,但是想获得锁B,刚好线程B持有锁B,想要获取锁A,两个线程互相等对方释放锁,就形成了死锁。

  • 简单示例
public class DeathLock {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    void methodA() throws InterruptedException {
        System.out.println("step in methodA");
        synchronized (lockA) {
            System.out.println("methodA getLockA");
            TimeUnit.SECONDS.sleep(1);
            synchronized (lockB) {
                System.out.println("methodA getLockB");
            }
        }
    }

    void methodB() throws InterruptedException {
        System.out.println("step in methodB");
        synchronized (lockB) {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("methodB getLockB");
            synchronized (lockA) {
                System.out.println("methodB getLockA");
            }
        }
    }

    public static void main(String[] args) {
        DeathLock de = new DeathLock();
        new Thread(() -> {
            try {
                de.methodA();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                de.methodB();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }).start();

    }
}

以上示例是很常见的锁顺序死锁,线程A获取锁A之后等待获取锁B,线程B在线程A获取锁A之后获取锁B等待锁A,于是就产生死锁。
上面的死锁的最简单的解决是在两个方法加上synchronized,让他们竞争同一把锁。

  • 使用API检测死锁
ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
        Runnable dlcheck = () -> {
            long[] threads = mbean.findDeadlockedThreads();
            if (threads != null) {
                ThreadInfo[] info = mbean.getThreadInfo(threads);
                System.out.println("detected dead threads:");
                for (ThreadInfo i : info) {
                    System.out.println(i.getThreadName());
                }
            }
        };
        ScheduledExecutorService schedule = Executors.newScheduledThreadPool(1);
        schedule.scheduleAtFixedRate(dlcheck, 5L, 10L, TimeUnit.SECONDS);
  • 示例2 协作对象锁顺序
class Taxi{
    private final Dispatcher dispatcher;
    private Point location;
    
    public Taxi(Dispatcher dispatcher){
        this.dispatcher = dispatcher;
    }
    
    public Point getLocation(){
        return this.location;
    }
    public synchronized void setLocation(Point location){
        this.location = location;
        dispatcher.notifyAvaliable(this);
    }
}

class Dispatcher{
    private final Set<Taxi> taxis;
    private final Set<Taxi> avaliableTaxis;
    public Dispacther(){
        taxis = new HashSet<Taxi>();
        avaliableTaxis = new HashSet<Taxi>();
    }
    
    public synchronized void notifyAvaliable(Taxi taxi){
        avaliableTaxis.add(taxi);
    }
    
    public synchronized Image getImage(){
        Image image = new Image();
        for(Taxi taxi : taxis){
            image.dawn(t.getLocation());
        }
        return image;
    }
}

考虑一个线程执行setLocation方法,这个方法先获取Taxi的锁,随后会尝试获取dispatcher的锁,另一个线程在执行getImage方法,这个方法先后需要获取dispatcher的锁和taxi的锁,这两个线程可能会出现死锁问题。要修改可以把内嵌锁拆到外层,使其先释放一个锁再获取另一个锁。
这是隐式的协作产生的死锁,不容易看出来。

  • 示例3
public void switch(Object a,Object b){
    synchronized(a){
        //dosomething
        synchronized(b){
            // dootherthing
        }
    }
}

考虑一个线程执行switch(a,b),另外一个线程执行switch(b,a)的情况,会发生死锁。

这种可以通过控制锁顺序来解决:

int ahash = system.identityHashCode(a);
int bhash = System.identityHashCode(b);
public void switch(Object a,Object b){
    if( ahash > bhash){
        synchronized(a){
         //dosomething
            synchronized(b){
            // dootherthing
            }
        }
    }else if( ahash < bhash){
        synchronized(b){
         //dosomething
            synchronized(a){
            // dootherthing
            }
        }
    }else{
        sysnchronized(this){
            synchronized(a){
                //dosomething
                synchronized(b){
                    // dootherthing
                }
            }
        }
    }
}

死锁的避免和分析

  • 每次只获取一个锁,避免锁顺序死锁
  • 使用支持定时的锁,可以从死锁中恢复过来。这个机制需要显式锁,内置锁没有这个功能
  • 使用线程转储信息分析死锁

参考资料

[1] Java并发编程实战

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 花开向阳百妩媚,无尽相思饮千愁。 夜伴钟声魂游离,良缘何处草木深? 2018年4月28日未时,情爱千秋。
    情爱千秋阅读 3,056评论 26 32
  • 001在年轻时做了想做的事情 在年轻时做了想做的事情,年轻时定义包括没病没痛,没太大硬性的限制时,因为这段时期完成...
    Jj1wong阅读 3,873评论 0 0
  • 《阴天》 雨欺骗了我的泪 虚伪的甘甜泛起心酸 你咬下一口的云 理想的天空开始想念 昨天晴朗的蓝 虫在铁轨缓缓蠕动 ...
    薄书阅读 1,319评论 0 2

友情链接更多精彩内容