java并发编程实战:避免活跃性危险笔记

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/sxj159753/article/details/78704002

死锁

锁顺序死锁

image

简单的锁顺序死锁示例:

public class LeftRightDeadlock {
    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight() {
        synchronized (left) {
            synchronized (right) {
                doSomething();
            }
        }
    }

    public void rightLeft() {
        synchronized (right) {
            synchronized (left) {
                doSomethingElse();
            }
        }
    }

    void doSomething() {
    }

    void doSomethingElse() {
    }
}

如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题。

动态锁顺序死锁

下面的例子中锁的顺序通过传递给transferMoney参数的顺序

 public static void transferMoney(Account fromAccount,
                                     Account toAccount,
                                     DollarAmount amount)
            throws InsufficientFundsException {
        synchronized (fromAccount) {
            synchronized (toAccount) {
                if (fromAccount.getBalance().compareTo(amount) < 0)
                    throw new InsufficientFundsException();
                else {
                    fromAccount.debit(amount);
                    toAccount.credit(amount);
                }
            }
        }
    }

一个从x转向y,一个从y转向x。

1 A: transferMoney(myAccount, yourAccount, 10);
2 B: transferMoney(yourAccount, myAccount, 20);

解决办法:
通过System.identityHashCode(obj) 来定义锁的顺序。


小结:hashCode和identityHashCode的区别

 String a = new String("hhh");
String b = new String("hhh");

打印发现对于String对象,只要a 和 b 的字符串是一样的,那么hashCode()方法返回的值必定相同,但是System.identityHashCode()方法不管什么情况下都不同。

  • hashCode() 是根据 内容 来产生hash值的。
  • System.identityHashCode() 是根据 内存地址 来产生hash值的。我们知道,new出来的String对象的内存地址是不一样的,所以hash值也不一样

解决办法:


    public void transferMoney(final Account fromAcct,
                              final Account toAcct,
                              final DollarAmount amount)
            throws InsufficientFundsException {
        class Helper {
            public void transfer() throws InsufficientFundsException {
                if (fromAcct.getBalance().compareTo(amount) < 0)
                    throw new InsufficientFundsException();
                else {
                    fromAcct.debit(amount);
                    toAcct.credit(amount);
                }
            }
        }
        int fromHash = System.identityHashCode(fromAcct);
        int toHash = System.identityHashCode(toAcct);

        if (fromHash < toHash) {
            synchronized (fromAcct) {
                synchronized (toAcct) {
                    new Helper().transfer();
                }
            }
        } else if (fromHash > toHash) {
            synchronized (toAcct) {
                synchronized (fromAcct) {
                    new Helper().transfer();
                }
            }
        } else {
        //使用tie-breaking第三种锁来控制,在获得两个锁之前就要获得这个锁。
            synchronized (tieLock) {
                synchronized (fromAcct) {
                    synchronized (toAcct) {
                        new Helper().transfer();
                    }
                }
            }
        }
    }

协作对象之间发生死锁

如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

package net.jcip.examples;

import java.util.*;

import net.jcip.annotations.*;

/**
 * CooperatingDeadlock
 * <p/>
 * Lock-ordering deadlock between cooperating objects
 *
 * @author Brian Goetz and Tim Peierls
 */
public class CooperatingDeadlock {
    // Warning: deadlock-prone!
    class Taxi {
        @GuardedBy("this") private Point location, destination;
        private final Dispatcher dispatcher;

        public Taxi(Dispatcher dispatcher) {
            this.dispatcher = dispatcher;
        }

        public synchronized Point getLocation() {
            return location;
        }

        public synchronized void setLocation(Point location) {
            this.location = location;
            if (location.equals(destination))
                dispatcher.notifyAvailable(this);
        }

        public synchronized Point getDestination() {
            return destination;
        }

        public synchronized void setDestination(Point destination) {
            this.destination = destination;
        }
    }

    class Dispatcher {
        @GuardedBy("this") private final Set<Taxi> taxis;
        @GuardedBy("this") private final Set<Taxi> availableTaxis;

        public Dispatcher() {
            taxis = new HashSet<Taxi>();
            availableTaxis = new HashSet<Taxi>();
        }

        public synchronized void notifyAvailable(Taxi taxi) {
            availableTaxis.add(taxi);
        }

        public synchronized Image getImage() {
            Image image = new Image();
            for (Taxi t : taxis)
                image.drawMarker(t.getLocation());
            return image;
        }
    }

    class Image {
        public void drawMarker(Point p) {
        }
    }
}

总结:正如在LeftRightDeadlock中发生的一样。

两个锁被两个线程以不同的顺序占有,产生死锁风险。

LeftRightDeadlock:

    public void leftRight() {
        synchronized (left) {
            synchronized (right) {
                doSomething();
            }
        }
    }

       public void rightLeft() {
        synchronized (right) {
            synchronized (left) {
                doSomethingElse();
            }
        }
    }

setLocation和getImage:

//先获取taxis锁,然后获取Dispathcer锁。
  public synchronized void setLocation(Point location) {
            this.location = location;
            if (location.equals(destination))
                //notifyAvailable也是一个加了锁的方法。
                dispatcher.notifyAvailable(this);
        }
//先获取了dispatcher锁,然后获取了每一个taxis锁。
 public synchronized Image getImage() {
        Image image = new Image();
        for (Taxi t : taxis)
            image.drawMarker(t.getLocation());
        return image;
    }

解决办法就是下一节说的开放调用。

开放调用——在调用某个方法时不需要持有锁

package net.jcip.examples;

import java.util.*;

import net.jcip.annotations.*;

/**
 * CooperatingNoDeadlock
 * <p/>
 * Using open calls to avoiding deadlock between cooperating objects
 *
 * @author Brian Goetz and Tim Peierls
 */
class CooperatingNoDeadlock {
    @ThreadSafe
    class Taxi {
        @GuardedBy("this") private Point location, destination;
        private final Dispatcher dispatcher;

        public Taxi(Dispatcher dispatcher) {
            this.dispatcher = dispatcher;
        }

        public synchronized Point getLocation() {
            return location;
        }

        public synchronized void setLocation(Point location) {
            boolean reachedDestination;
            synchronized (this) {
                this.location = location;
                reachedDestination = location.equals(destination);
            }
            if (reachedDestination)
                dispatcher.notifyAvailable(this);
        }

        public synchronized Point getDestination() {
            return destination;
        }

        public synchronized void setDestination(Point destination) {
            this.destination = destination;
        }
    }

    @ThreadSafe
    class Dispatcher {
        @GuardedBy("this") private final Set<Taxi> taxis;
        @GuardedBy("this") private final Set<Taxi> availableTaxis;

        public Dispatcher() {
            taxis = new HashSet<Taxi>();
            availableTaxis = new HashSet<Taxi>();
        }

        public synchronized void notifyAvailable(Taxi taxi) {
            availableTaxis.add(taxi);
        }

        //getImage不加锁
        public Image getImage() {
            Set<Taxi> copy;
            synchronized (this) {
                copy = new HashSet<Taxi>(taxis);
            }
            Image image = new Image();
            for (Taxi t : copy)
                image.drawMarker(t.getLocation());
            return image;
        }
    }

    class Image {
        public void drawMarker(Point p) {
        }
    }

}

资源死锁——两个线程分别持有彼此想要的资源而又不会释放

例子:任务执行需要连接两个数据库,两个任务分别连接了其中一个数据库,而又等待彼此释放另一个数据库的资源

 public class RenderPageTask implements Callable<String> {
        public String call() throws Exception {
            Future<String> header, footer;
            header = exec.submit(new LoadFileTask("header.html"));
            footer = exec.submit(new LoadFileTask("footer.html"));
            String page = renderBody();
            // 出现死锁 -- task waiting for result of subtask
            return header.get() + page + footer.get();
        }

        private String renderBody() {
            // Here's where we would actually render the page
            return "";
        }
    }

线程饥饿死锁——一个任务中提交另一个任务,并一直等待被提交任务完成

这些任务往往是产生线程饥饿死锁的主要来源,有界线程池 / 资源池与相互依赖的任务不能一起使用。

避免和诊断死锁

支持定时锁

显式使用Lock类中的定时tryLock功能来代替内置锁机制,显式锁则可以指定一个超时时限,在等待超过该时间后tryLock会返回一个失败信息。

通过线程转储信息来分析死锁

其他活跃性危险

饥饿

当线程访问他所需要的资源时却被永久的拒绝。以至于不能再继续执行。

Java中导致饥饿的原因:
- 高优先级线程吞噬所有的低优先级线程的CPU时间。
- 线程被永久堵塞在一个等待进入同步块的状态。
- 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。

在Java中实现公平性方案,需要:

  • 使用锁,而不是同步块。
  • 公平锁。
  • 注意性能方面。

http://ifeve.com/starvation-and-fairness/

糟糕的响应性

活锁

活锁指线程一直处于运行状态,却在做无用的功,线程本身要完成的任务却一直无法进展。

活锁并不会阻塞,而是一直尝试去获取需要的锁,不断的try,这种情况下线程并没有阻塞而是一直活跃的状态,但是在做无用功。

就像两个人不断在自己的方向上让路。两个人不断从同一方向移动,但是却在做无用功。

猜你喜欢

转载自blog.csdn.net/sxj159753/article/details/78704002