Eight, avoid dangerous activity

Between security and activity usually there is some kind of checks and balances. We use the locking mechanism to ensure thread-safe, but if the discussion of the use of the lock, the lock may result in the order of a deadlock (Lock-Ordering Deadlock). Similarly, we use the line pool and jelly, hug your number to limit the use of resources, but these are limited resources, it could result in a deadlock (Resource Deadlocks Tava application can not recover from a deadlock, so be sure to design exclude those conditions may lead to a deadlock arise.

一、死锁

Classic "Dining Philosophers" a good description of the problem a deadlock situation. 5 philosopher eat lunch, he sat at a round table. They chopstick 5 (instead of five pairs), and put a middle of each two chopsticks. Philosophers sometimes think, sometimes a meal. Everyone needs a pair of chopsticks to eat something, and after eating chopsticks back in place to continue thinking. Some chopsticks management algorithms enable everyone to eat something relatively timely manner (for example, a hungry philosopher will try to get two adjacent chopsticks, but if one is being used by another a philosopher, he would have to give up the resulting Flanagan chopsticks, then wait a few minutes and try again), but some algorithms it may cause some or all of the philosophers are "starving to death" (everyone jumped at the left of their own chopsticks, and then wait for the right of their own chopsticks empty out, but at the same time we do not have to get down the chopsticks). The latter case would produce a deadlock: Everyone else has the resources needed, while others have to wait for resources owned, and each person before obtaining all the required resources will not give up resources already have.
When a thread forever hold a lock, other threads are trying to get the lock, then they will never be blocked. 在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去。这种情况就是最简单的死锁形式(或者称为“抱死[Deadly Embrace]”)Where multiple threads because of dependency and lock loop forever wait.
When a set of Java threads deadlock, "the game" will end this - these threads can never be used again. According to different threads to complete the work, which may cause the application to a complete stop, or a particular subsystem to stop or reduce performance. The only way to recover is to suspend the application and restart it, and hope that the same thing will not happen again.
Unlike many other concurrent danger as the impact caused by the deadlock rarely immediately apparent. If a class is possible deadlock, then it does not mean that every time a deadlock occurs, but only that it is possible. When a deadlock occurs, often at the worst possible time - at high load conditions.

  • 锁顺序死锁

There is the risk of deadlock LeftRightDeadlock class. These methods and rightLeft leftRight respectively left and right lock locks. If a thread calls LeftRight, and another thread calls rightleft, and the operation of these two execution threads are interlaced, as shown below, they deadlock occurs.
>

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();
            }
        }
    }

}

The reason deadlock in LeftRightDeadlock are: two threads trying a different order to obtain the same lock. If in the same order to request a lock, then it will not appear dependent lock loop, therefore, it will not deadlock. If each need to lock the lock L and M threads are white to order to get the L and M, then no deadlock occurs.
如果所线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题。

  • 动态的锁顺序死锁

Sometimes, and not clearly know whether there is sufficient control in order to avoid the lock deadlock. Consider Listing seemingly innocuous code, it will transfer funds from one account to another. Before starting the transfer, you must first get the two locks Account object, in order to ensure to update the balance in the account by two atomic way, without destroying some of the invariance conditions, such as "balance of the account can not be negative."

public 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);
            }
        }
    }
}

How deadlock occurs in transferMoney in? All threads in the same order seems to get a lock, but 事实上锁的顺序取决于传递给transferMoney的参数顺序,these in turn depend on external input parameter order. If two threads tone transferMoney, wherein a thread transfers from the Y X, Y transfer money from another thread to X, then the deadlock occurs:

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

If you perform improper timing, it is possible to obtain a lock myAccount A and wait for a lock yourAccount, but this time hold yourAccount B locks, and is waiting for the lock myAccount.
由于我们无法控制参数的顺序,因此要解决这个问题,必须定义锁的顺序,并在整个应用程序中 都按照这个顺序来获取锁。
在制定锁的顺序时,可以使用System.identityHashCode方法,该方法将返回由Object.hashCode返回的值。Listing shows another version of transferMoney, System.identityHashCode defined sequence used in the lock release. While adding some new code, but it eliminates the possibility of deadlock.

public void transferMoney(final Account fromAcct,
                          final Account toAcct,
                          final DoliarAmount 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 {
        synchronized (tieLock) {
            synchronized (fromAcct) {
                synchronized (toAcct) {
                    new Helper().transfer();
                }
            }
        }
    }
}

In rare cases, two objects may have the same hash value, the order of the lock case must be determined by some arbitrary method, which may well be reintroduced into deadlock. To avoid this, you can use “加时赛(Tie- Breaking)” 锁. Before obtaining two Account lock, first get this "overtime" lock, thus ensuring that only one thread in an unknown order to obtain these two locks, thus eliminating the possibility of a deadlock occurs (as long as it is used consistently kind of mechanism). If the situation often occurs hash collision, then this technology could become a bottleneck concurrency (such {cases in the entire program with only one lock), but because of the emergence of a hash collision System.identityHashCode frequency is very low, so the technology at minimal cost, in exchange for maximum security.
如果在Account中包含一个唯一的、不可变的,并且具备可比性的键值,例如账号,那么 要制定锁的顺序就更加容易了:通过键值对对象进行排序,因而不需要使用“加时赛”锁。

  • 在协作对象之间发生的死锁

Some operations are not acquired as a plurality of locks in the LeftRightDeadlock or transferMoney so obvious, it does not necessarily lock the two must be acquired in the same process. Consider Listing mutual cooperation in two classes, the taxi dispatch system might need them. Taxi on behalf of a taxi object that contains the location and destination of two properties, Dispatcher on behalf of a taxi fleet.

// 注意容易发生死锁
class Taxi {
    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);
        }

    }
}
class Dispatcher {
    private final Set<Taxi> taxis;
    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 getlmage() {
        Image image = new Image();
        for (Taxi t : taxis){
            image.drawMarker(t.getLocation());
        }
        return image;
    }
}

Although there is no explicit method will acquire two locks, but setLocation and getlmage methods such as caller will receive two locks. 如果一个线程在收到GPS接收器的更新事件时调用setLocation,那么它将首先 更新出租车的位置,然后判断它是否到达了目的地。如果已经到达,它会通知Dispatcher:它需要一个新的目的地。因为setLocation和notifyAvailable都是同步方法,SetLocation therefore calling thread will first acquire Taxi lock, and then get the lock Dispatcher. 同样,Calling getImage Dispatcher thread will first acquire a lock, then each get a Taxi lock (every acquisition a) 。这与LeftRightDeadlock中的情况相同,两个线程按照不同的顺序来获取两个锁,因此就可能产生死锁。 在LeftRightDeadlock或transferMoney中,要查找死锁是比较简单的,只需要找出那些需 要获取两个锁的方法。然而要在Taxi和Dispatcher中査找死锁则比较困难:If you call an external method in the case of holding the lock, then you need to be alert deadlock. If you call an external method when holding the lock, active issues that will arise. In this external approach may acquire other locks (which may produce a deadlock), or block for too long, leading to lock other threads can not receive timely currently being held. `

  • 开放调用

如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。Depends on the class of open calls usually show a better behavior, and compared with those classes when calling the method need to hold locks, and easier to write. To avoid this deadlock by calling the open method similar to the method using encapsulation mechanism to provide thread-safe: although in no case can the package to ensure the construction of thread-safe procedures, but to use a package of program threads safety analysis than the analysis does not use encapsulation much easier. Similarly, the analysis relies on a totally open call activity program, activity analysis than those that do not rely on an open call procedure is simple. By using open calls as possible, it will be easier to find the code paths that need to acquire multiple locks and, therefore, easier to ensure a consistent order to acquire the lock.

在程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更易于对依赖于开放调用的程序进行死锁分析。

  • 资源死锁

Just as when a deadlock occurs when the name of the thread is waiting for each other to hold each other's locks and do not release the lock they have held, 当它们在相同的资源集合上等待时,也会发生死锁。
assuming that there are two resource pools, such as connection pooling two different databases. Semaphore resource pool usually be achieved when the resource pool is empty of blocking behavior. If a task needs to connect the two databases, and does not always follow the same order in both the resource request, the thread may hold A connection to the database D1 and waits for the database. D2 is connected, and the thread B is connected to the holder, and D2 is connected to the waiting D1. The smaller (larger the pool of resources, the possibility of this situation. If each resource pool has N connections, then not only requires N cycles waiting threads when a deadlock occurs, but also requires a lot of inappropriate execution timing.)

Another resource is based on the form of deadlock 线程饥饿死锁(Thread-Starvation Deadlock). An example: a task submit another task waiting to be executed and submitted to complete tasks in a single-threaded Executor. In this case, the first task will wait forever, and so that another task and all other tasks performed in this Executor are suspended. If some tasks need to wait for the results of other tasks, these tasks often are the main source of thread starvation deadlock, bounded thread pool / resource pool and interdependent tasks can not be used together.

二、死锁的避免与诊断

如果一个程序每次至多只能获得一个锁,那么就不会产生锁顺序死锁。Of course, this usually is not the reality, but if we can avoid this, then you can save a lot of work. If you must acquire multiple locks, the locks must be considered in the design of the order: minimize potential number of interactions lock, the protocol to follow when you write a formal document to acquire the lock and always follow these protocols.
In the program uses fine-grained locking, it is possible by using a two-stage strategy (Two-Part Strategy) to check the code in a deadlock: First, find out where the multiple locks (so this set as small as possible) then the global analysis of all of these instances, in order to ensure that they get the order locks are consistent throughout the program. Use as much as possible open calls, which can greatly simplify the analysis process. If all calls are open calls, then get to find multiple instances of lock is very simple, you can code review, or by means of automated source code analysis tools.

  • 支持定时的锁

Yet another technique may detect and recover from a deadlock deadlock, i.e., explicit use of the timing function tryLock Lock class to replace the built-locking mechanism. When using the built-in lock, just did not get the lock, it will wait forever, and 显式锁you can specify a timeout (Timeout), after waiting for more than the time tryLock returns a failure message. If the timeout ratio to obtain the lock time is much longer, then you can regain control after an accident to happen.
When the time lock fails, you do not need to know the reason for the failure. Perhaps because of a deadlock, perhaps when a thread holds the lock to enter an infinite loop error may also be the execution time of an operation far beyond your expectations. However, at least you can record the failure occurred, as well as other useful information about the operation of Ji and to restart your computer in a more gradual way, rather than shutting down the whole process.
Even without always using a timed lock in the entire system, using a timed locks to acquire multiple locks can effectively deal with the deadlock problem. If the timeout when acquiring the lock, you can release the lock, and then go back and try again after a period of time, thereby eliminating the deadlock condition occurs, the program recover. (This technique is only effective when simultaneously acquire two locks, if the request multiple locks in nested method call, even if you know that already hold the outer lock, can not release it.)

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

While the primary responsibility for preventing deadlock is your own, but the JVM is still to help identify the occurrence of a deadlock by a thread dump (Thread Dump). Thread dump, including each running thread stack trace information, which is similar to the stack trace information when an exception occurs. Locking thread dump also contains information such as what each thread holds the lock, which locks access to these stack frame, and blocked thread which is waiting for a lock acquisition. Before generating a thread dump, JVM will search to find the deadlock cycle by waiting for the diagram. If found a deadlock, the deadlock obtain the corresponding information, for example which locks and the thread, the position of the lock and which is located beneath the acquisition program relates to a deadlock.
If you use the explicit Lock classes rather than internal locks, then Java 5.0 does not support dump of information related to the Lock, Lock does not appear explicitly in the thread dump. Although Java 6 included support for deadlock detection and thread dump explicit Lock etc., but the information in the lock to get lower than the accuracy of information obtained in the built-in lock. Built-in lock and get the thread stack frame which they are associated, and it only explicit Lock thread associated with obtaining.

三、其他活跃性危险

In a concurrent program, there are some other dangerous activity, including: hunger, loss of signal lock and live.

  • 饥饿

当线程由于无法访问它所需要的资源而不能继续执行时,就发生了 “饥饿(Starvation)”。The most common trigger is resource hungry CPU clock cycles. If the priority of improper use of threads in Java applications, or perform some can not end structures (such as infinite loops, or wait indefinitely for a resource) while holding the lock, then it may lead to starvation because of other needs this will lock the thread can not get it.

Thread Priority Thread API defined in the thread scheduling only by reference. 10 defines the priority of the Thread API, JVM scheduling priority of the operating system necessary to map them to. This mapping is associated with a specific platform, so an operating system in two different Java priorities may be mapped to the same priority, and in another operating system may be mapped to a different priority level. In some operating systems, the number of priority if S is less than 10, so there are more Java priority is mapped to the same priority.

OS thread scheduler will endeavor to provide fair, good activity scheduling, even beyond the scope of the needs of the Java language specification. In most Java applications, all threads have the same priority Thread.NORM PRIORITY. Thread priority is not an intuitive mechanism, and by modifying the thread priority is usually caused by the effect is not obvious. When increasing the priority of a thread, it may not play any role, or it may make a thread scheduling priority of Gao in other threads, thus leading to starvation.

Usually, we try not to change the priority of the thread. Just change the priority of the thread, the behavior will be platform-dependent, and can lead to the risk of hunger occurred. You can often find a program calls Thread.sleep or Thread.yield in some strange place, this is because the program attempts to overcome problems or adjust priorities in response to questions, and try to get a low priority thread to perform more time.

要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。

  • 糟糕的响应

Another problem other than hunger is a bad response, CPU-intensive background tasks may still affect the responsiveness, as they will compete together with the CPU clock cycle event thread. In this case it can play the role of thread priority, then compute-intensive background tasks will affect responsiveness. If the work done by other threads are background tasks, they should lower their priority, thereby improving the responsiveness of Gao foreground program.
Adverse lock manager may also result in poor responsiveness. If a thread for a long time occupied a lock (perhaps working on a big container to iterate and perform computationally intensive process for each element), while the other threads that want to access the container would have to wait a long time.

  • 活锁

Livelock (Livelock) is another form of active issues, even though the problem does not block the thread, but they can not continue with
the line, because the thread will continue to repeat the same operation, and will always fail. Livelock usually occurs in the application process transactions message: If you can not successfully process a message, then the message handling mechanism will roll back the entire transaction, and it's back on the head of the queue. If there is an error message processor when processing a particular type of message and causes it to fail, then taken out and passed to an error processor whenever the message from the queue, transaction rollback occurs. As this message has been put back to the beginning of the queue, so the processor will be called repeatedly, and returns the same result. (Sometimes also known as a poison message, Poison Messaged.) Although the thread processing the message is not blocked, but it can not continue execution. This form of living locks are usually recovered from the error code caused by excessive, because it incorrectly irreparable error as an error repairable.

When a plurality of cooperating thread in response to each other to modify the respective states, and such that any thread can proceed, livelock occurs. It's like two people are too polite half way to meet face to face: they let each other out of the other side of the road, but met again in another way. So this way they avoid repeating it.

To resolve this issue live lock, need to introduce randomness in the retry mechanism. For example, in the network, if the two machines attempt to send a data packet using the same carrier, then the data packet will conflict. These machines are checked to the conflict, and are retransmitted again later. If they choose to retry after 1 second, then they conflict will happen and keep the conflict going, even when there is a lot of idle bandwidth, the packet can not be sent. To prevent this from happening, so that they are required to wait a random amount of time. [Ethernet protocol defines the way in exponential repeated clashes fallback mechanism, thereby reducing the risk of congestion and repeated failures occurred in the conflict between multiple machines. ) Concurrent application, can be effectively avoided by the livelock random length waiting time and rollback.

Guess you like

Origin blog.csdn.net/qq_27870421/article/details/90583273