前言:
本文是基于《Java多线程编程实战指南-核心篇》第五章个人理解,源码是摘抄作者的源码,源码会加上自己的理解。读书笔记目前笔者正在更新如下, 《Java多线程编程实战指南-核心篇》,《How Tomcat Works》,再到《spring 源码》解读。
等待与通知:wait/notify
单线程的变成中,如果程序需要在满足一定条件间下操作一个目标动作,就需要if语句,而在多线程中处理这种情况,保护条件可能只是暂时的,稍后其他线程可能更新了保护条件设计共享变量而使其成立,因此可以将当前线程暂停,知道保护条件得以成立时将其唤醒继续操作。伪代码如下:
//原子操作
atomic {
while(保护条件不成立) {
暂停当前线程;
}
doAction();
}
如果写成Java代码形式:
synchronized(someObject) {
while(保护条件不成立) {
someObject.wait();
}
doAction();
}
这边我个人理解,注意点有一个,就是上述操作需要具有原子性。
换个角度思考,如果上述操作不具有原子性会怎么样(即把synchronize这行注释掉),把步骤拆解:
1.等代码运行到someObject.wait()时候,会暂停当前线程,并释放所持有的someObject内部锁,线程生命周期会进入waiting状态,这时候someObject.wait()语句是不会返回,直到别的线程调用someObject.notifiy().
2.当别的线程调用someObject.notify()时候并且更新保护条件,notify会唤醒等待线程,someObject.wai()会申请someObject的内部锁,持有内部锁,该语句会返回。
3.while重新判断保护条件,但是因为操作不具有原子性,也就是在第二步到第三步过程中,有可能其他线程更改了保护条件,使得while条件再次不成立,所以重新在次进入wait语句。
4.同理doAction也是同样的道理,必须保证在执行目标动作之前,保护条件是成立状态,不然可能在执行doAction前一刻,其他线程对共享更新使得保护条件重新不成立。
因此上述while语句判断,以及doAction,以及wait调用需要放在同一个对象锁引导的临界区中。
而用Object.notify()通知,如下面伪代码:
synchronized(someObject){
//更新等待线程的保护条件设计的共享变量
updateShareState();
//唤醒其他线程
someObject.notify();
}
包含了更新共享变量和唤醒其他线程,由于一个线程只有持有一个对象的内部锁的情况下才能执行对象的notify,这也是为什么刚才说的第一步wait语句是会释放对象内部锁的,不然notify是无法调用的,详细参考下面wait内部实现伪代码。
notify还有一点需要注意就是,尽可能放在临界区结束的地方,也就是上面代码它在靠近结束的花括号之前的一句,这是因为当调用notify后,等待线程会被唤醒,但是notify本身并不会释放内部锁,所以如果它不靠近的话,等待线程可能又会拿不到内部锁,再次被暂停。
wait内部实现伪代码
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;//返回
}
实战案例
书里附了一个wait/notify的实战案例,其实我个人觉得这个实战案例不太好
案例是某分布式系统有个告警系统,将这些告警信息通过网络连接上报发送到告警服务器上,
AlarmAgent内部维护两个工作线程:一个工作线程负责与告警服务器建立网络连接,为网络连接线程,另个工作线程负责定时检查告警代理与告警服务器网络连接情况,为心跳线程
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();
}
}
参考文献
《Java多线程编程实战指南-核心篇》