到消息id,再发送出去。当然,实际还要考虑发送失败的情况,通过定时任务补偿。这就是本地消息表的一种实现思路,ThreadLocal存储了消息id,在事务提交后,再从ThreadLocal取出来发送消息。
首先说明,如下写法是不可取的,原因有:1.如果事务commit失败,mq还是发出去了 2.导致事务时间变长,事务内不宜处理其它耗时逻辑,如发送mq,调用接口等。
@Transactional
public void register() {
//插入数据
User user = new User();
userMapper.insert(user);
//发送消息,处理其它事情
mq.send(topic, user.getId());
}
改写如下:
@Transactional
public void register() {
//插入数据
User user = new User();
userMapper.insert(user);
UserMsg userMsg = new UserMsg();
userMsgMapper.insert(userMsg);
//不使用整个user对象,只存个id占用内存较少,user对象可以及时被回收
threadLocal.set(user.getId);
//注册回调
transCallbackService.afterCommit(() -> {
mqHandleService.handleUserRegister();
});
}
@Service
class TransCallbackService {
public void afterCommit(Runnable runnable) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
runnable.run();
}
});
}
}
}
@Service
class MqHandleService {
public void handleUserRegister() {
//从threadlocal获取id,再处理各种事情
mq.send(topic, threadLocal.get());
}
}
弱引用问题
java中对象引用有几种类型:强引用,弱引用,软引用和虚引用,它们的区别主要跟gc有关。
强引用:通常我们写的代码都是强引用,如:User user = new User(); user就是一个强引用,它指向了内存一块区域,只要user还是可达的,那么gc就不会回收对应的内存。如果user的作用域非常长,而且后面它又没有用到了,可以将它设置为null,这样gc可以快点回收对应的内存,当然现在jvm比较智能,可以自动分析完成这个事情。还有一个注意事项是,如果对象被如一个全局的HaspMap引用着,那么即使设置为null或者指向它的变量不可达了,它也不会被回收,如:
User user = new User();
HashMap map = new HashMap(); //被map引用着,map可达就不会被回收
map.put(user, 1);
user = null;
弱引用:如果一个对象只被弱引用对象引用着,那么它会在下一次gc被回收,弱引用使用WeakReference
类。如:
User user = new User();
WeakReference weakReference = new WeakReference(user);
user = null;
HashMap hashMap = new HashMap();
hashMap.put(weakReference,1);
System.gc();
当执行完user=null后,其对象内存区域就没有强引用指向它了,只有一个弱引用对象weakReference。接着执行gc,user原本指向的内存就会被回收。此时我们执行weakReference.get()将拿到一个null。 从这里可以看到如果使用弱引用,假设我们忘记从HashMap移除不需要的元素,它也会再下一次gc时被回收,防止内存泄漏。
软引用:在内存充足的条件下,不会被回收,只要在内存不足时才会被回收。
虚引用:随时可能被同时,主要用于跟踪gc,在对象被gc后会收到一个通知。
对于ThreadLocal来说,它里面的Entry继承了WeakReference
,会把key也就是ThreadLocal对象设置为弱引用。那为什么要这么做呢?
上面的例子我们刚提到,当你忘记remove的时候,使用弱引用可以防止内存泄漏,ThreadLocal也是出于这目的。假设key不是弱引用,开发者忘记remove,那么key就发生内存泄漏,只能等到Thread对象销毁时才回收,在一些使用线程池的场景下,Thread会一直复用,就会导致内存一直回收不了。
public void test() {
inner();
System.gc();
//Thread.currentThread.threadLocals
}
private void inner() {
TestClass testClass = new TestClass();
testClass.set(new User());
}
class TestClass {
ThreadLocal threadLocal = new ThreadLocal();
public void set(Object value) {
threadLocal.set(value);
}
}
//更简单的例子
public void test() {
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set(new User());
threadLocal = null;
System.gc();
//Thread.currentThread.threadLocals
}
如上代码,往ThreadLocal放了一个User对象,此时ThreadLocalMap就维护一个key为threadLocal,value为User的Entry,当inner方法执行完,threadLocal已经不可达,但它的内存区域还被Entry引用着,并且没法再访问到,如果是强引用,就出现内存泄漏。如果是弱引用,在gc后,我们观察Thread.currentThread.threadLocals就可以发现,它的referent变成了null,被回收了。但作为value的User对象是强引用,不会被回收。到这里有些面试官就会问,为什么value不也设置为弱引用呢?
如下代码:
public void test() {
TestClass test