Java multi-threaded programming-inter-thread collaboration wait/notify

Foreword:

This article is based on my personal understanding of Chapter 5 of "Java Multi-Threaded Programming Practical Guide - Core Chapter". The source code is excerpted from the author's source code. The source code will add your own understanding. The reading notes are currently being updated by the author as follows, "Java Multi-Threaded Programming Practical Guide - Core Chapter", "How Tomcat Works", and then "spring source code" Interpretation.

Waiting and notification: wait/notify

In single-threaded programming, if the program needs to operate a target action when certain conditions are met, an if statement is needed. When handling this situation in multi-threaded programming, the protection condition may only be temporary, and other threads may update it later. The protection condition is designed with shared variables to make it true, so the current thread can be suspended and waked up to continue the operation until the protection condition is true. The pseudocode is as follows:

//原子操作
atomic {

   while(保护条件不成立) {
        暂停当前线程;
   }

   doAction();
}

If written in Java code form:

synchronized(someObject) {
    while(保护条件不成立) {
        someObject.wait();
   }
   doAction();
}

My personal understanding here is that one thing to note is that the above operations need to be atomic.

Think about it from another angle, what would happen if the above operation is not atomic (that is, comment out the synchronize line), and break down the steps:

1. When the code runs to someObject.wait(), the current thread will be suspended and the someObject internal lock held will be released. The thread life cycle will enter the waiting state. At this time, the someObject.wait() statement will not return until Other threads call someObject.notifiy().

2. When other threads call someObject.notify() and update the protection conditions, notify will wake up the waiting thread, someObject.wai() will apply for the internal lock of someObject, hold the internal lock, and the statement will return.

3. while re-judges the protection condition, but because the operation is not atomic, that is, during the second to third steps, other threads may change the protection condition, making the while condition not true again, so wait is entered again. statement.

4. The same applies to doAction. It must be ensured that the protection condition is established before executing the target action. Otherwise, other threads may update the sharing just before executing doAction, causing the protection condition to become invalid again.

Therefore, the above while statement judgment, as well as doAction and wait calls need to be placed in the critical section guided by the same object lock.

Use Object.notify() to notify, as shown in the following pseudo code:

synchronized(someObject){
    //更新等待线程的保护条件设计的共享变量
    updateShareState();

    //唤醒其他线程
    someObject.notify();
}

It includes updating shared variables and waking up other threads. Since a thread can only execute the object's notify if it holds the internal lock of an object, this is why the first step of the wait statement just mentioned will release the object's internal lock, otherwise notify cannot be called. For details, please refer to the pseudocode of the internal implementation of wait below.

Another thing to note about notify is that it should be placed as close to the end of the critical section as possible, that is, the sentence in the above code before the curly brace near the end. This is because when notify is called, the waiting thread will be awakened, but notify itself The internal lock will not be released, so if it is not close, the waiting thread may not be able to get the internal lock and be suspended again.

Wait internal implementation pseudo code

Public void wait() {

    //执行线程必须持有当前对象对应的内部锁
    if(!Thread.holdsLock(this)){
        Throws new IllegalMonitorStateException();
    }

    if(当前对象不在等待集中){
    //将当前线程加入当前对象等待集中

       addToWaitSet(Thread.currentThread());
    }

    atomic{//原子操作开始
        //释放当前对象的内部锁

        releaseLock(this);
        //暂停当前线程
        block(Thread.currentThread());

    }//原子操作结束

    //再次申请当前对象的内部锁
    acquireLock(this);
    //将当前线程从当前对象的等待及中移除
    removeFromWaitSet(Thread.currentThread());
    return;//返回
}

Practical cases

The book includes a practical case of wait/notify. In fact, I personally feel that this practical case is not very good.

The case is that a distributed system has an alarm system, which reports the alarm information and sends it to the alarm server through a network connection.

AlarmAgent maintains two working threads internally: one working thread is responsible for establishing a network connection with the alarm server, which is the network connection thread, and the other working thread is responsible for regularly checking the network connection between the alarm agent and the alarm server, which is the heartbeat thread.

public class CaseRunner5_1 {
  final static AlarmAgent alarmAgent;
  static {
    alarmAgent = AlarmAgent.getInstance();
    alarmAgent.init();
  }

  public static void main(String[] args) throws InterruptedException {

    alarmAgent.sendAlarm("Database offline!");
    Tools.randomPause(12000);
    alarmAgent.sendAlarm("XXX service unreachable!");
  }
}
import java.util.Random;


public class AlarmAgent {
    // 保存该类的唯一实例
    private final static AlarmAgent INSTANCE = new AlarmAgent();
    // 是否连接上告警服务器
    private boolean connectedToServer = false;
    // 心跳线程,用于检测告警代理与告警服务器的网络连接是否正常
    private final HeartbeartThread heartbeatThread = new HeartbeartThread();

    private AlarmAgent() {
        // 什么也不做
    }

    public static AlarmAgent getInstance() {
        return INSTANCE;
    }

    public void init() {
        connectToServer();
        heartbeatThread.setDaemon(true);
        heartbeatThread.start();
    }

    private void connectToServer() {
        // 创建并启动网络连接线程,在该线程中与告警服务器建立连接
        new Thread() {
            @Override
            public void run() {
                doConnect();
            }
        }.start();
    }

    private void doConnect() {
        // 模拟实际操作耗时
        Tools.randomPause(100);
        synchronized (this) {
            connectedToServer = true;
            // 连接已经建立完毕,通知以唤醒告警发送线程
            notify();
        }
    }

    public void sendAlarm(String message) throws InterruptedException {
        synchronized (this) {
            // 使当前线程等待直到告警代理与告警服务器的连接建立完毕或者恢复
            while (!connectedToServer) {

                Debug.info("Alarm agent was not connected to server.");

                wait();
            }

            // 真正将告警消息上报到告警服务器
            doSendAlarm(message);
        }
    }

    private void doSendAlarm(String message) {
        // ...
        Debug.info("Alarm sent:%s", message);
    }

    // 心跳线程
    class HeartbeartThread extends Thread {
        @Override
        public void run() {
            try {
                // 留一定的时间给网络连接线程与告警服务器建立连接
                Thread.sleep(1000);
                while (true) {
                    if (checkConnection()) {
                        connectedToServer = true;
                    } else {
                        connectedToServer = false;
                        Debug.info("Alarm agent was disconnected from server.");

                        // 检测到连接中断,重新建立连接
                        connectToServer();
                    }
                    Thread.sleep(2000);
                }
            } catch (InterruptedException e) {
                // 什么也不做;
            }
        }

        // 检测与告警服务器的网络连接情况
        private boolean checkConnection() {
            boolean isConnected = true;
            final Random random = new Random();

            // 模拟随机性的网络断链
            int rand = random.nextInt(1000);
            if (rand <= 500) {
                isConnected = false;
            }
            return isConnected;
        }
    }
}
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Debug {
    private static ThreadLocal<SimpleDateFormat> sdfWrapper = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        }

    };

    enum Label {
        INFO("INFO"),
        ERR("ERROR");
        String name;

        Label(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    // public static void info(String message) {
    // printf(Label.INFO, "%s", message);
    // }

    public static void info(String format, Object... args) {
        printf(Label.INFO, format, args);
    }

    public static void info(boolean message) {
        info("%s", message);
    }

    public static void info(int message) {
        info("%d", message);
    }

    public static void error(String message, Object... args) {
        printf(Label.ERR, message, args);
    }

    public static void printf(Label label, String format, Object... args) {
        SimpleDateFormat sdf = sdfWrapper.get();
        @SuppressWarnings("resource")
        final PrintStream ps = label == Label.INFO ? System.out : System.err;
        ps.printf('[' + sdf.format(new Date()) + "][" + label.getName()
                + "]["
                + Thread.currentThread().getName() + "]:" + format + " %n", args);
    }
}
import sun.misc.Unsafe;

import java.io.*;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class Tools {
    private static final Random rnd = new Random();
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void startAndWaitTerminated(Thread... threads)
            throws InterruptedException {
        if (null == threads) {
            throw new IllegalArgumentException("threads is null!");
        }
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
    }

    public static void startThread(Thread... threads) {
        if (null == threads) {
            throw new IllegalArgumentException("threads is null!");
        }
        for (Thread t : threads) {
            t.start();
        }
    }

    public static void startAndWaitTerminated(Iterable<Thread> threads)
            throws InterruptedException {
        if (null == threads) {
            throw new IllegalArgumentException("threads is null!");
        }
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
    }

    public static void randomPause(int maxPauseTime) {
        int sleepTime = rnd.nextInt(maxPauseTime);
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void randomPause(int maxPauseTime, int minPauseTime) {
        int sleepTime = maxPauseTime == minPauseTime ? minPauseTime : rnd
                .nextInt(maxPauseTime - minPauseTime) + minPauseTime;
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            ((Field) f).setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void silentClose(Closeable... closeable) {
        if (null == closeable) {
            return;
        }
        for (Closeable c : closeable) {
            if (null == c) {
                continue;
            }
            try {
                c.close();
            } catch (Exception ignored) {
            }
        }
    }

    public static void split(String str, String[] result, char delimeter) {
        int partsCount = result.length;
        int posOfDelimeter;
        int fromIndex = 0;
        String recordField;
        int i = 0;
        while (i < partsCount) {
            posOfDelimeter = str.indexOf(delimeter, fromIndex);
            if (-1 == posOfDelimeter) {
                recordField = str.substring(fromIndex);
                result[i] = recordField;
                break;
            }
            recordField = str.substring(fromIndex, posOfDelimeter);
            result[i] = recordField;
            i++;
            fromIndex = posOfDelimeter + 1;
        }
    }

    public static void log(String message) {
        LOGGER.log(Level.INFO, message);
    }

    public static String md5sum(final InputStream in) throws NoSuchAlgorithmException, IOException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] buf = new byte[1024];
        try (DigestInputStream dis = new DigestInputStream(in, md)) {
            while (-1 != dis.read(buf))
                ;
        }
        byte[] digest = md.digest();
        BigInteger bigInt = new BigInteger(1, digest);
        String checkSum = bigInt.toString(16);

        while (checkSum.length() < 32) {
            checkSum = "0" + checkSum;
        }
        return checkSum;
    }

    public static String md5sum(final File file) throws NoSuchAlgorithmException, IOException {
        return md5sum(new BufferedInputStream(new FileInputStream(file)));
    }

    public static String md5sum(String str) throws NoSuchAlgorithmException, IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(str.getBytes("UTF-8"));
        return md5sum(in);
    }

    public static void delayedAction(String prompt, Runnable action, int delay/* seconds */) {
        Debug.info("%s in %d seconds.", prompt, delay);
        try {
            Thread.sleep(delay * 1000);
        } catch (InterruptedException ignored) {
        }
        action.run();
    }

    public static Object newInstanceOf(String className) throws InstantiationException,
            IllegalAccessException, ClassNotFoundException {
        return Class.forName(className).newInstance();
    }

}

references

"Java Multi-Threaded Programming Practical Guide-Core"

Guess you like

Origin blog.csdn.net/u012895183/article/details/133156725