Java concurrent programming article series

How to solve the problem of visibility and order in Java

The premise

In the example at the end of the third article, when two accounts need to be locked before transferring money, deadlock may occur in this case, I put the code snippet from the previous chapter in the following:

public class Account {
    / / the balance
    private Long money;
    public synchronized void transfer(Account target, Long money) {
        synchronized(this) {(1)
            synchronized (target) {    (2)
                this.money -= money;
                if (this.money < 0) {
                    // throw exception} target.money += money; }}}}Copy the code

If account A transfers 100 yuan to account Band account B transfers 100 yuan to account Aat the same time, when the thread A of account A transfers to code (1), it obtains the lock of account A, and when the thread B of account B transfers to code (1), it obtains the lock of account B. When thread A and thread B reach code (2), they are waiting for each other to release the lock, but synchronized is A blocking lock and will not release the lock until the code block has been executed. Thus, thread A and thread B are locked against each other, and neither of them can release the lock. When the day comes when you go to reboot the app… This phenomenon is called deadlock. A deadlock is defined as a “permanent” block caused by a group of threads competing for resources waiting for each other. The diagram below:

Find deadlock information

Here I will take A basic deadlock program as an example, create two threads, thread A to get lock A, sleep for 1 second to get lock B; After thread B obtains lock B, it will sleep for 1 second to obtain lock A. The following code can be used to create a deadlock:

public class DeadLock extends Thread {
    private String first;
    private String second;
    public DeadLock(String name, String first, String second) {
        super(name); / / thread
        this.first = first;
        this.second = second;
    }

    public  void run(a) {
        synchronized (first) {
            System.out.println(this.getName() + "Lock obtained:" + first);
            try {
                Thread.sleep(1000L); // The thread sleeps for 1 second
                synchronized (second) {
                    System.out.println(this.getName() + "Lock obtained:"+ second); }}catch (InterruptedException e) {
                // Do nothing}}}public static void main(String[] args) throws InterruptedException {
        String lockA = "lockA";
        String lockB = "lockB";
        DeadLock threadA = new DeadLock("ThreadA", lockA, lockB);
        DeadLock threadB = new DeadLock("ThreadB", lockB, lockA);
        threadA.start();
        threadB.start();
        threadA.join(); // Wait for thread 1 to completethreadB.join(); }}Copy the code

A deadlock will occur after you run the program. Then use the JPS command (jps.exe in the JDK /bin directory) as follows:

C: \ Program Files \ Java \ jdk1.8.0 _221 \ bin > JPS-l
24416 sun.tools.jps.Jps
24480 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
1624
20360 org.jetbrains.jps.cmdline.Launcher
9256
9320 page2.DeadLock
18188
Copy the code

You can find the deadlocked process ID 9320 and run the jstack (jstack.exe in the JDK /bin directory) command to view the deadlock information.

C: \ Program Files \ Java \ jdk1.8.0 _221 \ bin > jstack 9320"ThreadB" #13 prio=5 os_prio=0 tid=0x000000001e48c800 nid=0x51f8 waiting for monitor entry [0x000000001f38f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at page2.DeadLock.run(DeadLock.java:19)
        - waiting to lock <0x000000076b99c198> (a java.lang.String)
        - locked <0x000000076b99c1d0> (a java.lang.String)

"ThreadA" #12 prio=5 os_prio=0 tid=0x000000001e48c000 nid=0x3358 waiting for monitor entry [0x000000001f28f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at page2.DeadLock.run(DeadLock.java:19)
        - waiting to lock <0x000000076b99c1d0> (a java.lang.String)
        - locked <0x000000076b99c198> (a java.lang.String)
Copy the code

This way we can see the information that a deadlock has occurred. Deadlocks were found, but the only way to resolve the deadlock was to restart the application.

How do I avoid deadlocks

1. Get locks in a fixed order

If all threads acquire locks in a fixed order, there will be no lock order deadlocks in the program. There are many ways to verify the consistency of the lock order. If the locked object has an increasing ID field (unique, immutable, comparable), then it is much easier to obtain the lock order in order of the smallest ID. To illustrate the transfer example, the code is as follows:

public class Account {
    // id (increment)
    private Integer id;
    / / the balance
    private Long money;
    public synchronized void transfer(Account target, Long money) {
        Account account1;
        Account account2;
        if (this.id < target.id) {
            account1 = this;
            account2 = target;
        } else {
            account1 = target;
            account2 = this;
        }

        synchronized(account1) {
            synchronized (account2) {
                this.money -= money;
                if (this.money < 0) {
                    // throw exception} target.money += money; }}}}Copy the code

If the object does not have a unique, immutable, comparable field (such as an incrementalized ID), then the hash returned by the system.identityHashCode () method can be used for comparison. The comparison can be similar to example 1 above. System.identityhashcode () hashes occur, but are very rare. So the technology offers maximum security at minimal cost. Tip: The system.identityHashcode () method only returns the default hash value, whether or not you override the object’s hashCode method.

2. Apply for all resources at once

Just get the resource lock for both the roll-out account and the roll-in account. After performing the transfer operation, the resource locks for both the transfer and transfer accounts are released. There will be no deadlocks. However, synchronized can lock only one resource lock at a time. Therefore, a LockAllocator must be set up. The code is as follows:

/** Lock allocator (singleton class) */
public class LockAllocator {
    private final List<Object> lock = new ArrayList<Object>();
    /** Apply for resource lock */
    public synchronized boolean lock(Object object1, Object object2) {
        if (lock.contains(object1) || lock.contains(object2)) {
            return false;
        }

        lock.add(object1);
        lock.add(object2);
        return true;
    }
    /** Release the resource lock */
    public synchronized void unlock(Object object1, Object object2) { lock.remove(object1); lock.remove(object2); }}public class Account {
    / / the balance
    private Long money;
    // Lock allocator
    private LockAllocator lockAllocator;
    
    public void transfer(Account target, Long money) {
        try {
            // Loop the lock until it is successfully acquired
            while(! lockAllocator.lock(this, target)) {
            }

            synchronized (this) {synchronized (target){
                    this.money -= money;
                    if (this.money < 0) {
                        // throw exception} target.money += money; }}}finally {
            / / releases the lock
            lockAllocator.unlock(this, target); }}}Copy the code

Use the while loop to continuously acquire the lock until the lock is acquired, or you can set the lock to sleep for xx milliseconds after the lock is acquired, or some other optimization method. The lock must be released using a try-finally method. Avoid lock release failure.

3. Try to obtain lock resources

In Java, the Lock interface defines an abstract set of Lock operations. Unlike synchronized, which uses a built-in Lock and waits until it is acquired, display Lock provides an unconditional, pollable, timed, and interruptible Lock acquisition operation. All Lock and unlock operations are displayed (built-in Lock Lock and unlock operations are implicit), this article will not expand to show the Lock Lock (of course interested friends can baidu first).

conclusion

Deadlocks can be a serious problem in a production environment. Although restarting an application to solve deadlocks is a production environment, it is expensive and deadlocks can still occur after restarting an application. Therefore, it is necessary to be very careful to avoid deadlocks when writing concurrent programs. There should be more ways to avoid deadlocks, but I’m sorry to tell you about them. If there are other options, please leave a message. Thank you very much for reading. Thank you.

Java Concurrent Programming Practice 05: Accidentally deadlock, What to do? Under what circumstances can a Java program deadlock? How to locate and repair?

Personal blog: colablog.cn/

If my article helps you, you can follow my wechat official number and share the article with you as soon as possible