您好,登錄后才能下訂單哦!
假設(shè)我們有一個(gè)數(shù)據(jù)庫連接管理類:
class ConnectionManager {
private static Connection connect = null;
private static String url = System.getProperty("URL");
public static Connection openConnection() {
if(connect == null){
try {
connect = DriverManager.getConnection(url);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connect;
}
public static void closeConnection() {
if(connect!=null) {
try {
connect.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
如果這個(gè)類被用在多線程環(huán)境內(nèi),則會(huì)存在線程安全問題,那么可以對(duì)這兩個(gè)方法添加synchronized關(guān)鍵字進(jìn)行同步處理,不過這樣會(huì)大大降低程序的性能,也可以將connection變成局部變量:
class ConnectionManager {
private Connection connect = null;
public Connection openConnection(String url) {
if(connect == null){
try {
connect = DriverManager.getConnection(url);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connect;
}
public void closeConnection() {
if(connect!=null) {
try {
connect.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
class ConnectionManagerTest {
private String url = System.getProperty("URL");
public void insert() {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection(this.url);
//使用connection進(jìn)行操作
connectionManager.closeConnection();
}
public void update() {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection(this.url);
//使用connection進(jìn)行操作
connectionManager.closeConnection();
}
}
每個(gè)CURD方法都創(chuàng)建新的數(shù)據(jù)庫連接會(huì)造成數(shù)據(jù)庫的很大壓力,這里可以有兩種解決方案:
使用連接池管理連接,既不是每次都創(chuàng)建、銷毀連接,而是從一個(gè)連接池里借出可用的連接,用完將其歸還。
class ConnectionManager {
private static String url = System.getProperty("URL");
private static ThreadLocalconnectionHolder = ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection(url);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
});
public static Connection openConnection() {
return connectionHolder.get();
}
public static void closeConnection() {
Connection connect = connectionHolder.get();
if(connect!=null) {
try {
connect.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
另外還可以用到其他需要每個(gè)線程管理一份自己的資源副本的地方:An Introduction to ThreadLocal in Java
這里面涉及到三種對(duì)象的映射:Thread-ThreadLocal對(duì)象-ThreadLocal中存的具體內(nèi)容,既然是每個(gè)線程都會(huì)有一個(gè)資源副本,那么這個(gè)從ThreadLocal對(duì)象到存儲(chǔ)內(nèi)容的映射自然就會(huì)存在Thread對(duì)象里:
ThreadLocal.ThreadLocalMap threadLocals = null;
而ThreadLocal類只是提供了訪問這個(gè)Map的接口:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
這個(gè)ThreadLocalMap是ThreadLocal的內(nèi)部類,實(shí)現(xiàn)了一個(gè)類似HashMap的功能,其內(nèi)部維護(hù)了一個(gè)Entry數(shù)組,下標(biāo)就是通過ThreadLocal對(duì)象的threadLocalHashCode計(jì)算得來。這個(gè)Entry繼承自WeakReference,實(shí)現(xiàn)對(duì)key,也就是ThreadLocal的弱引用:
static class Entry extends WeakReference<threadlocal
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
</threadlocal
內(nèi)存模型圖如下:
當(dāng)ThreadLocal Ref出棧后,由于ThreadLocalMap中Entry對(duì)ThreadLocal只是弱引用,所以ThreadLocal對(duì)象會(huì)被回收,Entry的key會(huì)變成null,然后在每次get/set/remove ThreadLocalMap中的值的時(shí)候,會(huì)自動(dòng)清理key為null的value,這樣value也能被回收了。
注意:如果ThreadLocal Ref一直沒有出棧(例如上面的connectionHolder,通常我們需要保證ThreadLocal為單例且全局可訪問,所以設(shè)為static),具有跟Thread相同的生命周期,那么這里的虛引用便形同虛設(shè)了,所以使用完后記得調(diào)用ThreadLocal.remove將其對(duì)應(yīng)的value清除。
另外,由于ThreadLocalMap中只對(duì)ThreadLocal是弱引用,對(duì)value是強(qiáng)引用,如果ThreadLocal因?yàn)闆]有其他強(qiáng)引用而被回收,之后也沒有調(diào)用過get/set,那么就會(huì)產(chǎn)生內(nèi)存泄露,
在使用線程池時(shí),線程會(huì)被復(fù)用,那么里面保存的ThreadLocalMap同樣也會(huì)被復(fù)用,會(huì)造成線程之間的資源沒有被隔離,所以在線程歸還回線程池時(shí)要記得調(diào)用remove方法。
上面提到ThreadLocalMap是自己實(shí)現(xiàn)的類似HashMap的功能,當(dāng)出現(xiàn)Hash沖突(通過兩個(gè)key對(duì)象的hash值計(jì)算得到同一個(gè)數(shù)組下標(biāo))時(shí),它沒有采用鏈表模式,而是采用的線性探測(cè)的方法,既當(dāng)發(fā)生沖突后,就線性查找數(shù)組中空閑的位置。
當(dāng)數(shù)組較大時(shí),這個(gè)性能會(huì)很差,所以建議盡量控制ThreadLocal的數(shù)量。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。