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并发编程实战

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容

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