欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中ThreadLocal避免內(nèi)存泄漏的方法詳解

 更新時間:2023年05月19日 08:24:45   作者:越走越遠(yuǎn)的風(fēng)  
ThreadLocal是Java中的一個線程本地存儲機(jī)制,它允許每個線程擁有一個獨(dú)立的本地存儲空間,用于存儲該線程的變量,本文主要介紹了ThreadLocal如何避免內(nèi)存泄漏,需要的朋友可以參考下

ThreadLocal簡介

ThreadLocal 是 Java 中的一個線程本地存儲機(jī)制,它允許每個線程擁有一個獨(dú)立的本地存儲空間,用于存儲該線程的變量。ThreadLocal 提供了一種簡單的方式來解決多線程環(huán)境下共享變量的問題,避免了在多線程環(huán)境下出現(xiàn)的線程安全問題。

ThreadLocal簡單用法

public class ThreadLocalDemo {  
    private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);  
    
    //當(dāng)前值+1
    public static void increment() {  
        int value = threadLocal.get();  
        threadLocal.set(value + 1);  
    }  

    public static void main(String[] args) throws InterruptedException {  
        for (int i = 0; i < 10; i++) {  
            new Thread(() -> {  
                String threadName = Thread.currentThread().getName();  
                increment();  
                System.out.println(threadName + " 當(dāng)前threadLocal的值為:" + threadLocal.get());  
            }).start();  
        }  
    }  
  
}

輸出結(jié)果為

Thread-0 當(dāng)前threadLocal的值為:1
Thread-5 當(dāng)前threadLocal的值為:1
Thread-3 當(dāng)前threadLocal的值為:1
Thread-4 當(dāng)前threadLocal的值為:1
Thread-6 當(dāng)前threadLocal的值為:1
Thread-2 當(dāng)前threadLocal的值為:1
Thread-9 當(dāng)前threadLocal的值為:1
Thread-1 當(dāng)前threadLocal的值為:1
Thread-7 當(dāng)前threadLocal的值為:1
Thread-8 當(dāng)前threadLocal的值為:1

我們發(fā)現(xiàn)每個線程雖然都共享同一個threadLocal實(shí)例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨(dú)立的值,這是因?yàn)槲覀兺ㄟ^ThreadLocal為每一個線程提供了單獨(dú)的副本。

使用場景

spring事務(wù)模板類

//使用示例
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

//查看源碼
public static TransactionStatus currentTransactionStatus() throws NoTransactionException {  
    TransactionInfo info = currentTransactionInfo();  
    if (info == null || info.transactionStatus == null) {  
        throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");  
    }  
    return info.transactionStatus;  
}

//currentTransactionInfo方法
@Nullable  
protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {  
    return transactionInfoHolder.get();  
}

//這里使用了ThreadLocal
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =  
new NamedThreadLocal<>("Current aspect-driven transaction");

HttpServletRequest

項(xiàng)目中要獲取當(dāng)前HttpServletRequest可以使用

HttpServletRequest request =  
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

我們來看看RequestContextHolder.getRequestAttributes()方法

@Nullable  
public static RequestAttributes getRequestAttributes() {  
    RequestAttributes attributes = requestAttributesHolder.get();  
    if (attributes == null) {  
        attributes = inheritableRequestAttributesHolder.get();  
    }  
    return attributes;  
}

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =  
new NamedThreadLocal<>("Request attributes");  
  
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =  
new NamedInheritableThreadLocal<>("Request context");

aop調(diào)用鏈傳遞

LCN/seata都是使用ThreadLocal傳遞調(diào)用鏈的,這里就不展開講了。

內(nèi)存泄漏與內(nèi)存溢出

內(nèi)存泄漏

內(nèi)存泄漏指的是程序中存在某些對象或資源沒有被妥善地釋放,導(dǎo)致這些對象或資源一直占用著內(nèi)存,而無法被回收。隨著時間的推移,這些未釋放的對象或資源會越來越多,最終耗盡系統(tǒng)的內(nèi)存資源,導(dǎo)致系統(tǒng)崩潰。

常見的內(nèi)存泄漏包括:

  • 對象被創(chuàng)建后,沒有及時被銷毀,成為垃圾對象。
  • 沒有正確關(guān)閉IO資源。
  • 緩存沒有被清空。
  • 靜態(tài)集合類對象未刪除引用。
  • 單例模式下對象未及時釋放等。

內(nèi)存溢出

內(nèi)存溢出指的是程序在申請內(nèi)存時,無法獲得足夠的內(nèi)存空間,導(dǎo)致程序無法正常運(yùn)行。通常情況下,當(dāng)程序需要使用的內(nèi)存超過了系統(tǒng)能夠提供的內(nèi)存時,就會發(fā)生內(nèi)存溢出。

常見的內(nèi)存溢出包括:

  • 堆內(nèi)存溢出:由于創(chuàng)建了過多的對象或者某些對象太大,導(dǎo)致堆內(nèi)存不足。
  • 棧內(nèi)存溢出:由于方法調(diào)用過多或者某些方法的遞歸調(diào)用層數(shù)過多,導(dǎo)致棧內(nèi)存不足。
  • 永久代內(nèi)存溢出:由于創(chuàng)建了過多的類或者字符串,導(dǎo)致永久代內(nèi)存不足。

區(qū)別

內(nèi)存泄漏和內(nèi)存溢出的區(qū)別在于它們發(fā)生的原因和表現(xiàn)形式。內(nèi)存泄漏是指對象或者資源無法被妥善釋放,導(dǎo)致系統(tǒng)資源浪費(fèi),而內(nèi)存溢出則是指系統(tǒng)不能分配所需內(nèi)存,導(dǎo)致程序崩潰或者異常。通常情況下,內(nèi)存泄漏會逐漸消耗系統(tǒng)資源,而內(nèi)存溢出則是突然發(fā)生的。

解決內(nèi)存泄漏的方法是找到未被正確釋放的對象或資源,并手動進(jìn)行釋放。而解決內(nèi)存溢出的方法則需要優(yōu)化程序代碼,減少內(nèi)存使用量,或增加系統(tǒng)內(nèi)存大小等方式來解決。

java強(qiáng)軟弱虛

Java中的引用類型有四種:強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)。它們之間的主要區(qū)別在于對象被垃圾回收時的行為不同。

強(qiáng)引用

強(qiáng)引用是默認(rèn)類型的引用,當(dāng)我們通過“new”關(guān)鍵字創(chuàng)建一個對象時,該對象會被分配到堆內(nèi)存中,并且默認(rèn)情況下,該對象的引用是強(qiáng)引用。只要強(qiáng)引用存在,垃圾回收器就不會將其回收。

例如:

Object obj = new Object();

在上面的代碼中,obj是一個強(qiáng)引用,因此只有當(dāng)obj變量被顯示地設(shè)置為null時,才能使對象成為垃圾,等待垃圾回收器收集。

軟引用

軟引用可以讓對象存活更長時間,直到內(nèi)存不足時才回收它。如果垃圾回收器需要更多的內(nèi)存,則會回收只被軟引用引用的對象。當(dāng)一個對象只被軟引用引用時,它會被保留在內(nèi)存中,直到系統(tǒng)內(nèi)存不夠用或者垃圾回收器需要更多空間為止。通過軟引用可以實(shí)現(xiàn)一些緩存功能。

例如:

SoftReference<Object> softRef = new SoftReference<>(new Object());

在上面的代碼中,softRef是一個軟引用,當(dāng)垃圾回收器需要內(nèi)存時,它可以將該對象回收,并釋放所占用的內(nèi)存。

弱引用

弱引用比軟引用生命期更短,當(dāng)一個對象只被弱引用引用時,當(dāng)垃圾回收器運(yùn)行時,不管當(dāng)前內(nèi)存是否充足,都會將其回收。弱引用通常用于實(shí)現(xiàn)緩存機(jī)制或者觀察者模式。

例如:

WeakReference<Object> weakRef = new WeakReference<>(new Object());

在上面的代碼中,weakRef是一個弱引用,這意味著垃圾回收器可以隨時將該對象回收,而無需考慮系統(tǒng)內(nèi)存是否充足。

虛引用

虛引用也稱為幽靈引用,與其他三種引用方式不同,它并不會決定對象是否能存活。如果一個對象只有虛引用,那么就像沒有任何引用一樣,它的內(nèi)存會被回收,但是在回收之前會調(diào)用finalize()方法。虛引用主要用于管理DirectBuffer的生命周期。

例如:

PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), null);

在上面的代碼中,phantomRef是一個虛引用,當(dāng)垃圾回收器發(fā)現(xiàn)該對象的內(nèi)存已被回收時,它會將其插入隊列中,并在下一次調(diào)用垃圾回收器時通知引用對象被回收了。

ThreadLocal原理

ThreadLocal

ThreadLocal是一個泛型類,它提供了get()、set()和remove()方法來獲取、設(shè)置和刪除當(dāng)前線程的變量副本。它的原理是在每個Thread對象中都有一個ThreadLocalMap類型的私有變量threadLocals,該變量存儲著當(dāng)前線程所對應(yīng)的所有ThreadLocal變量的值。

public T get() { 
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();  
    //獲取當(dāng)前線程的ThreadLocalMap變量
    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);  
}

public void remove() {  
    ThreadLocalMap m = getMap(Thread.currentThread());  
    if (m != null)  
        m.remove(this);  
}

ThreadLocalMap

ThreadLocalMap是ThreadLocal的內(nèi)部類,它實(shí)際上就是一個HashMap,用于存儲當(dāng)前線程所對應(yīng)的所有ThreadLocal變量的值。每個ThreadLocal對象都會被保存在ThreadLocalMap中,并且使用ThreadLocal作為key來訪問它的變量值。這樣做的好處是每個線程都可以獨(dú)立維護(hù)自己的數(shù)據(jù),而不會與其他線程產(chǎn)生沖突。

ThreadLocalMap getMap(Thread t) {  
    return t.threadLocals;  
}

ThreadLocal.ThreadLocalMap threadLocals = null;

這里需要注意的是ThreadLocalMap每個元素都是Entry,而它是用弱引用對象作為key存儲在ThreadLocalMap中

static class Entry extends WeakReference<ThreadLocal<?>> {  
    /** The value associated with this ThreadLocal. */  
    Object value;  

    Entry(ThreadLocal<?> k, Object v) {  
        super(k);  
        value = v;  
    }  
}

實(shí)現(xiàn)原理

當(dāng)我們通過ThreadLocal類創(chuàng)建一個新的變量時,實(shí)際上是在當(dāng)前線程的threadLocals變量中創(chuàng)建了一個新的Entry對象,該對象的key是ThreadLocal對象本身,value則是我們設(shè)置的變量值。這個Entry對象會存儲在ThreadLocalMap中。

當(dāng)我們需要在當(dāng)前線程中訪問這個變量時,ThreadLocal會根據(jù)當(dāng)前線程獲取對應(yīng)的ThreadLocalMap對象,并根據(jù)ThreadLocal對象作為key來查找該變量的值。由于每個線程都有自己獨(dú)立的ThreadLocalMap對象,因此不同線程之間的變量互不干擾。

ThreadLocal內(nèi)存泄漏原因

每個ThreadLocal對象都會被存儲在當(dāng)前線程的ThreadLocalMap中,并且使用ThreadLocal對象作為key來訪問它的變量值。由于使用的是弱引用對象作為key,當(dāng)一個ThreadLocal對象沒有被任何線程引用時,該對象就會被回收。

但是,即使ThreadLocal對象已經(jīng)被回收,對應(yīng)的變量副本仍然存在于該線程的ThreadLocalMap中。這是因?yàn)門hreadLocalMap內(nèi)部使用了強(qiáng)引用對象(Entry對象)來引用變量副本,只有在當(dāng)前線程被回收時,ThreadLocalMap中對應(yīng)的Entry才會被回收。

也就是說,ThreadLocal對象雖然使用的是弱引用,但是與之關(guān)聯(lián)的變量副本卻是通過強(qiáng)引用對象間接引用的,因此在ThreadLocal對象被回收后,其變量副本可能不會立刻被回收。如果我們沒有手動調(diào)用remove()方法將變量副本從ThreadLocalMap中清除,那么它就會一直存在于內(nèi)存中,從而導(dǎo)致內(nèi)存泄漏問題。

ThreadLocal內(nèi)存泄漏常見場景

ThreadLocal內(nèi)存泄漏的原因主要是由于線程復(fù)用導(dǎo)致的。

線程池

當(dāng)我們使用線程池時,如果在線程中使用了ThreadLocal變量,那么該變量并不會被自動清除。線程池中的線程是可以被重復(fù)利用的,如果我們在一個線程中使用了ThreadLocal變量,并且沒有在該線程結(jié)束前手動清除它,那么這個變量將會一直存在于ThreadLocalMap中,即使該線程已經(jīng)被回收,這就會導(dǎo)致內(nèi)存泄漏。

長時間持有

如果我們在一個線程中創(chuàng)建了ThreadLocal變量,并且一直持有它卻不使用,這也會導(dǎo)致內(nèi)存泄漏問題。在這種情況下,由于該變量一直存在于ThreadLocalMap中,即使該線程已經(jīng)被回收,該變量也無法被釋放,最終會導(dǎo)致內(nèi)存泄漏。

ThreadLocal避免內(nèi)存泄漏方法

為了避免ThreadLocal內(nèi)存泄漏問題,我們可以采取以下措施:

  • 在使用完ThreadLocal變量后,應(yīng)該盡快調(diào)用remove()方法將其從ThreadLocalMap中清除,以便讓垃圾回收器回收它們。
  • 將ThreadLocal變量定義成private static類型的,并且在使用完之后手動清除,以避免線程重用時引起的內(nèi)存泄漏問題。
  • 不要在線程池中使用ThreadLocal變量,如果必須使用,應(yīng)該在使用完后手動清理。

總結(jié)

雖然ThreadLocal可以解決線程安全問題,但是在使用完之后需要手動清除,以避免線程重用時引起的內(nèi)存泄漏問題。

以上就是Java中ThreadLocal避免內(nèi)存泄漏的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Java ThreadLocal避免內(nèi)存泄漏的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論