神奇的bug
前言:我寫的明明是單例,可是爲什麼初始化了二次?
今天寫的這個bug和單例設計模式有關。 所謂單例設計模式,這個不多說,詳情可以自行百度。
spring中的單例
springboot的control就是單例的,關於這個可以看看這個文章
spring的controller默認是單例還是多例
場景
在我這次的項目中遇到這樣一個功能需求:我需要設計一個單例的類,這個類裏面有個阻塞隊列。
日誌的生產和消費就需要放置在隊列中。
日誌的生產我會監聽logback的事件,如果logback打印日誌,就把打印的內容發送到隊列裏面。
然後webscoket消費數據。
說白了,這裏面的隊列需要單例的,而且起到中轉數據的作用。
代碼
這是對隊列進行一個簡單的封裝,如下
package cn.xiejx.jfun.config.websocket;
import cn.xiejx.jfun.vo.LogMessage;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 創建一個阻塞隊列,作爲日誌系統輸出的日誌的一個臨時載體
* @author jie
* @date 2018-12-24
*/
public class LoggerQueue {
/**
* 隊列大小
*/
public static final int QUEUE_MAX_SIZE = 10000;
private static LoggerQueue alarmMessageQueue = new LoggerQueue();
/**
* 阻塞隊列
*/
private BlockingQueue blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
private LoggerQueue() {
System.out.println("loggerquque:"+this.getClass().getClassLoader().toString());
}
public static LoggerQueue getInstance() {
return alarmMessageQueue;
}
/**
* 消息入隊
* @param log
* @return
*/
public boolean push(LogMessage log) {
return this.blockingQueue.add(log);
}
/**
* 消息出隊
*
* @return
*/
public LogMessage poll() {
LogMessage result = null;
try {
result = (LogMessage) this.blockingQueue.take();
System.out.println("消費一條消息"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
產生的問題
其實上面這個代碼是沒問題的。
我的項目是springboot,上面的單例使用的是沒有用spring註解實現(springboot也有可以使用註解來使用單例)。
然後這邊消費數據的時候調用take方法。
take方法沒有數據是會阻塞的。
而數據已經有add進去了。
按理說可以消費到數據纔是,可是數據卻沒有被消費。
分析
使用了java的一些分析工具,把堆dump了出來。
看到了消費數據的線程是處於等待狀態。
最傻的方法用System.out.println也分析出線程一直在阻塞中(卡在take那裏)
進一步分析
我確信單例的實現是沒有問題的,按理說,數據add進去,應該是可以消費到的纔對。
斷點繼續分析,不看不知道。
發現了消費和生產數據的時候,getInstance返回的對象的hashCode碼不一樣。
也就是說單例被破壞了,生產數據放在了a隊列裏面,而消費數據卻要在b隊列裏面拿數據。
爲什麼呢?我的代碼明明是單例啊啊啊啊!!!
解決問題
分析,如果是相同的類加載器,不可能出現這種情況。
那麼我就在初始化的時候加上下面的代碼
System.out.println("loggerquque:"+this.getClass().getClassLoader().toString());
發現這個類果然被初始化了2次。確實不是‘單例’了(不同的類加載器分別初始化)。
輸出如下:
....
loggerquque:sun.misc.Launcher$AppClassLoader@18b4aac2
....
loggerquque:org.springframework.boot.devtools.restart.classloader.RestartClassLoader@2a5810d2
原來是這個熱部署的鍋!!!
撥雲見日!!!
pom.xml文件果斷去除熱部署依賴先(暫時我只能這麼解決)
<!--熱部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.0.2.RELEASE</version>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--配置熱啓動-->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
重啓idea,問題解決。
參考鏈接
說明
上面的代碼非原創,來自大佬的項目。
碼雲:https://gitee.com/elunez/eladmin-qt
github:https://github.com/elunez/eladmin-qd
這裏感謝作者的開源。