激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 聊聊java并發編程的十個坑

聊聊java并發編程的十個坑

2022-03-09 22:48蘇三說技術蘇三呀 Java教程

說實話,在Java中并發編程不光理解起來比較費勁,使用起來更容易踩坑。今天重點跟大家一起聊聊并發編程的十個坑,希望對你有幫助。

對于從事后端開發的同學來說,并發編程肯定再熟悉不過了。

說實話,在Java中并發編程是一大難點,至少我是這么認為的。不光理解起來比較費勁,使用起來更容易踩坑。

聊聊java并發編程的十個坑

不信,讓繼續往下面看。

今天重點跟大家一起聊聊并發編程的十個坑,希望對你有幫助。

聊聊java并發編程的十個坑

1. SimpleDateFormat線程不安全

在java8之前,我們對時間的格式化處理,一般都是用的SimpleDateFormat類實現的。例如:

@Service public class SimpleDateFormatService { public Date time(String time) throws ParseException { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return dateFormat.parse(time);
    }
}

如果你真的這樣寫,是沒問題的。

就怕哪天抽風,你覺得dateFormat是一段固定的代碼,應該要把它抽取成常量。

于是把代碼改成下面的這樣:

@Service public class SimpleDateFormatService { private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public Date time(String time) throws ParseException { return dateFormat.parse(time);
    }
}

dateFormat對象被定義成了靜態常量,這樣就能被所有對象共用。

如果只有一個線程調用time方法,也不會出現問題。

但Serivce類的方法,往往是被Controller類調用的,而Controller類的接口方法,則會被tomcat的線程池調用。換句話說,可能會出現多個線程調用同一個Controller類的同一個方法,也就是會出現多個線程會同時調用time方法的情況。

而time方法會調用SimpleDateFormat類的parse方法:

@Override public Date parse(String text, ParsePosition pos) { ... Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); ... } catch (IllegalArgumentException e) { pos.errorIndex = start; pos.index = oldStart; return null;
    } return parsedDate;
} 

 

 

該方法會調用establish方法:

 

Calendar establish(Calendar cal) { ... //1.清空數據 cal.clear(); //2.設置時間 cal.set(...); //3.返回 return cal;
}

其中的步驟1、2、3是非原子操作。

但如果cal對象是局部變量還好,壞就壞在parse方法調用establish方法時,傳入的calendar是SimpleDateFormat類的父類DateFormat的成員變量:

public abstract class DateFormat extends Forma { .... protected Calendar calendar; ... }

這樣就可能會出現多個線程,同時修改同一個對象即:dateFormat,他的同一個成員變量即:Calendar值的情況。

這樣可能會出現,某個線程設置好了時間,又被其他的線程修改了,從而出現時間錯誤的情況。

那么,如何解決這個問題呢?

  • SimpleDateFormat類的對象不要定義成靜態的,可以改成方法的局部變量。
  • 使用ThreadLocal保存SimpleDateFormat類的數據。
  • 使用java8的DateTimeFormatter類。

2. 雙重檢查鎖的漏洞

單例模式無論在實際工作,還是在面試中,都出現得比較多。

我們都知道,單例模式有:餓漢模式和懶漢模式兩種。

餓漢模式代碼如下:

public class SimpleSingleton { //持有自己類的引用 private static final SimpleSingleton INSTANCE = new SimpleSingleton(); //私有的構造方法 private SimpleSingleton() {
    } //對外提供獲取實例的靜態方法 public static SimpleSingleton getInstance() { return INSTANCE;
    }
}

使用餓漢模式的好處是:沒有線程安全的問題,但帶來的壞處也很明顯。

private static final SimpleSingleton INSTANCE = new SimpleSingleton();

一開始就實例化對象了,如果實例化過程非常耗時,并且最后這個對象沒有被使用,不是白白造成資源浪費嗎?

還真是啊。

這個時候你也許會想到,不用提前實例化對象,在真正使用的時候再實例化不就可以了?

這就是我接下來要介紹的:懶漢模式。

具體代碼如下:

public class SimpleSingleton2 { private static SimpleSingleton2 INSTANCE; private SimpleSingleton2() {
    } public static SimpleSingleton2 getInstance() { if (INSTANCE == null) { INSTANCE = new SimpleSingleton2();
        } return INSTANCE;
    }
}

示例中的INSTANCE對象一開始是空的,在調用getInstance方法才會真正實例化。

嗯,不錯不錯。但這段代碼還是有問題。

假如有多個線程中都調用了getInstance方法,那么都走到 if (INSTANCE == null) 判斷時,可能同時成立,因為INSTANCE初始化時默認值是null。這樣會導致多個線程中同時創建INSTANCE對象,即INSTANCE對象被創建了多次,違背了只創建一個INSTANCE對象的初衷。

為了解決餓漢模式和懶漢模式各自的問題,于是出現了:雙重檢查鎖。

具體代碼如下:

public class SimpleSingleton4 { private static SimpleSingleton4 INSTANCE; private SimpleSingleton4() {
    } public static SimpleSingleton4 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton4.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton4();
                }
            }
        } return INSTANCE;
    }
}

需要在synchronized前后兩次判空。

但我要告訴你的是:這段代碼有漏洞的。

有什么問題?

public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 synchronized (SimpleSingleton4.class) {//2 if (INSTANCE == null) {//3 INSTANCE = new SimpleSingleton4();//4 }
        }
    } return INSTANCE;//5 }

getInstance方法的這段代碼,我是按1、2、3、4、5這種順序寫的,希望也按這個順序執行。

但是java虛擬機實際上會做一些優化,對一些代碼指令進行重排。重排之后的順序可能就變成了:1、3、2、4、5,這樣在多線程的情況下同樣會創建多次實例。重排之后的代碼可能如下:

public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 if (INSTANCE == null) {//3 synchronized (SimpleSingleton4.class) {//2 INSTANCE = new SimpleSingleton4();//4 }
        }
    } return INSTANCE;//5 }

原來如此,那有什么辦法可以解決呢?

答:可以在定義INSTANCE是加上volatile關鍵字。具體代碼如下:

public class SimpleSingleton7 { private volatile static SimpleSingleton7 INSTANCE; private SimpleSingleton7() {
    } public static SimpleSingleton7 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton7.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton7();
                }
            }
        } return INSTANCE;
    }
}

volatile關鍵字可以保證多個線程的可見性,但是不能保證原子性。同時它也能禁止指令重排。

雙重檢查鎖的機制既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。

3. volatile的原子性

從前面我們已經知道volatile,是一個非常不錯的關鍵字,它能保證變量在多個線程中的可見性,它也能禁止指令重排,但是不能保證原子性。

使用volatile關鍵字禁止指令重排,前面已經說過了,這里就不聊了。

可見性主要體現在:一個線程對某個變量修改了,另一個線程每次都能獲取到該變量的最新值。

先一起看看反例:

public class VolatileTest extends Thread { private boolean stopFlag = false; public boolean isStopFlag() { return stopFlag;
    } @Override public void run() { try { Thread.sleep(300);
        } catch (InterruptedException e) { e.printStackTrace();

        } stopFlag = true; System.out.println(Thread.currentThread().getName() + " stopFlag = " + stopFlag);
    } public static void main(String[] args) { VolatileTest vt = new VolatileTest(); vt.start(); while (true) { if (vt.isStopFlag()) { System.out.println("stop"); break;
            }
        }
    }
}

上面這段代碼中,VolatileTest是一個Thread類的子類,它的成員變量stopFlag默認是false,在它的run方法中修改成了true。

然后在main方法的主線程中,用vt.isStopFlag()方法判斷,如果它的值是true時,則打印stop關鍵字。

那么,如何才能讓stopFlag的值修改了,在主線程中通過vt.isStopFlag()方法,能夠獲取最新的值呢?

正例如下:

public class VolatileTest extends Thread { private volatile boolean stopFlag = false; public boolean isStopFlag() { return stopFlag;
    } @Override public void run() { try { Thread.sleep(300);
        } catch (InterruptedException e) { e.printStackTrace();

        } stopFlag = true; System.out.println(Thread.currentThread().getName() + " stopFlag = " + stopFlag);
    } public static void main(String[] args) { VolatileTest vt = new VolatileTest(); vt.start(); while (true) { if (vt.isStopFlag()) { System.out.println("stop"); break;
            }
        }
    }
}

用volatile關鍵字修飾stopFlag即可。

下面重點說說volatile的原子性問題。

使用多線程給count加1,代碼如下:

public class VolatileTest { public volatile int count = 0; public void add() { count++;
    } public static void main(String[] args) { final VolatileTest test = new VolatileTest(); for (int i = 0; i < 20; i++) { new Thread() { @Override public void run() { for (int j = 0; j < 1000; j++) { test.add();
                    }
                }

                ;
            }.start();
        } while (Thread.activeCount() > 2) { //保證前面的線程都執行完 Thread.yield();
        } System.out.println(test.count);
    }
}

執行結果每次都不一樣,但可以肯定的是count值每次都小于20000,比如:19999。

這個例子中count是成員變量,雖說被定義成了volatile的,但由于add方法中的count++是非原子操作。在多線程環境中,count++的數據可能會出現問題。

由此可見,volatile不能保證原子性。

那么,如何解決這個問題呢?

答:使用synchronized關鍵字。

改造后的代碼如下:

public class VolatileTest { public int count = 0; public synchronized void add() { count++;
    } public static void main(String[] args) { final VolatileTest test = new VolatileTest(); for (int i = 0; i < 20; i++) { new Thread() { @Override public void run() { for (int j = 0; j < 1000; j++) { test.add();
                    }
                }

                ;
            }.start();
        } while (Thread.activeCount() > 2) { //保證前面的線程都執行完 Thread.yield();
        } System.out.println(test.count);
    }
}

4. 死鎖

死鎖可能是大家都不希望遇到的問題,因為一旦程序出現了死鎖,如果沒有外力的作用,程序將會一直處于資源競爭的假死狀態中。

死鎖代碼如下:

public class DeadLockTest { public static String OBJECT_1 = "OBJECT_1"; public static String OBJECT_2 = "OBJECT_2"; public static void main(String[] args) { LockA lockA = new LockA(); new Thread(lockA).start(); LockB lockB = new LockB(); new Thread(lockB).start();
    }

} class LockA implements Runnable { @Override public void run() { synchronized (DeadLockTest.OBJECT_1) { try { Thread.sleep(500); synchronized (DeadLockTest.OBJECT_2) { System.out.println("LockA");
                }
            } catch (InterruptedException e) { e.printStackTrace();
            }
        }
    }
} class LockB implements Runnable { @Override public void run() { synchronized (DeadLockTest.OBJECT_2) { try { Thread.sleep(500); synchronized (DeadLockTest.OBJECT_1) { System.out.println("LockB");
                }
            } catch (InterruptedException e) { e.printStackTrace();
            }
        }
    }
}

 

 

一個線程在獲取OBJECT_1鎖時,沒有釋放鎖,又去申請OBJECT_2鎖。而剛好此時,另一個線程獲取到了OBJECT_2鎖,也沒有釋放鎖,去申請OBJECT_1鎖。由于OBJECT_1和OBJECT_2鎖都沒有釋放,兩個線程將一起請求下去,陷入死循環,即出現死鎖的情況。

 

那么如果避免死鎖問題呢?

4.1 縮小鎖的范圍

出現死鎖的情況,有可能是像上面那樣,鎖范圍太大了導致的。

那么解決辦法就是縮小鎖的范圍。

具體代碼如下:

class LockA implements Runnable { @Override public void run() { synchronized (DeadLockTest.OBJECT_1) { try { Thread.sleep(500);
            } catch (InterruptedException e) { e.printStackTrace();
            }
        } synchronized (DeadLockTest.OBJECT_2) { System.out.println("LockA");
        }
    }
} class LockB implements Runnable { @Override public void run() { synchronized (DeadLockTest.OBJECT_2) { try { Thread.sleep(500);
            } catch (InterruptedException e) { e.printStackTrace();
            }
        } synchronized (DeadLockTest.OBJECT_1) { System.out.println("LockB");
        }
    }
}

在獲取OBJECT_1鎖的代碼塊中,不包含獲取OBJECT_2鎖的代碼。同時在獲取OBJECT_2鎖的代碼塊中,也不包含獲取OBJECT_1鎖的代碼。

4.2 保證鎖的順序

出現死鎖的情況說白了是,一個線程獲取鎖的順序是:OBJECT_1和OBJECT_2。而另一個線程獲取鎖的順序剛好相反為:OBJECT_2和OBJECT_1。

那么,如果我們能保證每次獲取鎖的順序都相同,就不會出現死鎖問題。

具體代碼如下:

class LockA implements Runnable { @Override public void run() { synchronized (DeadLockTest.OBJECT_1) { try { Thread.sleep(500); synchronized (DeadLockTest.OBJECT_2) { System.out.println("LockA");
                }
            } catch (InterruptedException e) { e.printStackTrace();
            }
        }
    }
} class LockB implements Runnable { @Override public void run() { synchronized (DeadLockTest.OBJECT_1) { try { Thread.sleep(500); synchronized (DeadLockTest.OBJECT_2) { System.out.println("LockB");
                }
            } catch (InterruptedException e) { e.printStackTrace();
            }
        }
    }
}

兩個線程,每個線程都是先獲取OBJECT_1鎖,再獲取OBJECT_2鎖。

5. 沒釋放鎖

在java中除了使用synchronized關鍵字,給我們所需要的代碼塊加鎖之外,還能通過Lock關鍵字加鎖。

使用synchronized關鍵字加鎖后,如果程序執行完畢,或者程序出現異常時,會自動釋放鎖。

但如果使用Lock關鍵字加鎖后,需要開發人員在代碼中手動釋放鎖。

例如:

public class LockTest { private final ReentrantLock rLock = new ReentrantLock(); public void fun() { rLock.lock(); try { System.out.println("fun");
        } finally { rLock.unlock();
        }
    }
}

代碼中先創建一個ReentrantLock類的實例對象rLock,調用它的lock方法加鎖。然后執行業務代碼,最后再finally代碼塊中調用unlock方法。

但如果你沒有在finally代碼塊中,調用unlock方法手動釋放鎖,線程持有的鎖將不會得到釋放。

6. HashMap導致內存溢

出HashMap在實際的工作場景中,使用頻率還是挺高的,比如:接收參數,緩存數據,匯總數據等等。

但如果你在多線程的環境中使用HashMap,可能會導致非常嚴重的后果。

@Service public class HashMapService { private Map<Long, Object> hashMap = new HashMap<>(); public void add(User user) { hashMap.put(user.getId(), user.getName());
    }
}

 

 

在HashMapService類中定義了一個HashMap的成員變量,在add方法中往HashMap中添加數據。在controller層的接口中調用add方法,會使用tomcat的線程池去處理請求,就相當于在多線程的場景下調用add方法。

 

在jdk1.7中,HashMap使用的數據結構是:數組+鏈表。如果在多線程的情況下,不斷往HashMap中添加數據,它會調用resize方法進行擴容。該方法在復制元素到新數組時,采用的頭插法,在某些情況下,會導致鏈表會出現死循環。

死循環最終結果會導致:內存溢出。

此外,如果HashMap中數據非常多,會導致鏈表很長。當查找某個元素時,需要遍歷某個鏈表,查詢效率不太高。

為此,jdk1.8之后,將HashMap的數據結構改成了:數組+鏈表+紅黑樹。

如果同一個數組元素中的數據項小于8個,則還是用鏈表保存數據。如果大于8個,則自動轉換成紅黑樹。

為什么要用紅黑樹?

答:鏈表的時間復雜度是O(n),而紅黑樹的時間復雜度是O(logn),紅黑樹的復雜度是優于鏈表的。

既然這樣,為什么不直接使用紅黑樹?

答:樹節點所占存儲空間是鏈表節點的兩倍,節點少的時候,盡管在時間復雜度上,紅黑樹比鏈表稍微好一些。但是由于紅黑樹所占空間比較大,HashMap綜合考慮之后,認為節點數量少的時候用占存儲空間更多的紅黑樹不劃算。

jdk1.8中HashMap就不會出現死循環?

答:錯,它在多線程環境中依然會出現死循環。在擴容的過程中,在鏈表轉換為樹的時候,for循環一直無法跳出,從而導致死循環。

那么,如果想多線程環境中使用HashMap該怎么辦呢?

答:使用ConcurrentHashMap。

7. 使用默認線程池

我們都知道jdk1.5之后,提供了ThreadPoolExecutor類,用它可以自定義線程池。

線程池的好處有很多,比如:

  • 降低資源消耗:避免了頻繁的創建線程和銷毀線程,可以直接復用已有線程。而我們都知道,創建線程是非常耗時的操作。
  • 提供速度:任務過來之后,因為線程已存在,可以拿來直接使用。
  • 提高線程的可管理性:線程是非常寶貴的資源,如果創建過多的線程,不僅會消耗系統資源,甚至會影響系統的穩定。使用線程池,可以非常方便的創建、管理和監控線程。

當然jdk為了我們使用更便捷,專門提供了:Executors類,給我們快速創建線程池。

該類中包含了很多靜態方法:

  • newCachedThreadPool:創建一個可緩沖的線程,如果線程池大小超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
  • newFixedThreadPool:創建一個固定大小的線程池,如果任務數量超過線程池大小,則將多余的任務放到隊列中。
  • newScheduledThreadPool:創建一個固定大小,并且能執行定時周期任務的線程池。
  • newSingleThreadExecutor:創建只有一個線程的線程池,保證所有的任務安裝順序執行。

在高并發的場景下,如果大家使用這些靜態方法創建線程池,會有一些問題。

那么,我們一起看看有哪些問題?

  • newFixedThreadPool:允許請求的隊列長度是Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
  • newSingleThreadExecutor:允許請求的隊列長度是Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
  • newCachedThreadPool:允許創建的線程數是Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。

那我們該怎辦呢?

優先推薦使用ThreadPoolExecutor類,我們自定義線程池。

具體代碼如下:

ExecutorService threadPool = new ThreadPoolExecutor( 8, //corePoolSize線程池中核心線程數 10, //maximumPoolSize 線程池中最大線程數 60, //線程池中線程的最大空閑時間,超過這個時間空閑線程將被回收 TimeUnit.SECONDS,//時間單位 new ArrayBlockingQueue(500), //隊列 new ThreadPoolExecutor.CallerRunsPolicy()); //拒絕策略

 

 

順便說一下,如果是一些低并發場景,使用Executors類創建線程池也未嘗不可,也不能完全一棍子打死。在這些低并發場景下,很難出現OOM問題,所以我們需要根據實際業務場景選擇。

 

8. @Async注解的陷阱

之前在java并發編程中實現異步功能,一般是需要使用線程或者線程池。

線程池的底層也是用的線程。

而實現一個線程,要么繼承Thread類,要么實現Runnable接口,然后在run方法中寫具體的業務邏輯代碼。

開發spring的大神們,為了簡化這類異步操作,已經幫我們把異步功能封裝好了。spring中提供了@Async注解,我們可以通過它即可開啟異步功能,使用起來非常方便。

具體做法如下:

(1) 在springboot的啟動類上面加上@EnableAsync注解。

@EnableAsync @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args);
    }
}

 

 

(2) 在需要執行異步調用的業務方法加上@Async注解。

 

@Service public class CategoryService { @Async public void add(Category category) { //添加分類 }
}

(3) 在controller方法中調用這個業務方法。

@RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; @PostMapping("/add") public void add(@RequestBody category) { categoryService.add(category);
     }
}

這樣就能開啟異步功能了。

是不是很easy?

但有個壞消息是:用@Async注解開啟的異步功能,會調用AsyncExecutionAspectSupport類的doSubmit方法。

聊聊java并發編程的十個坑

默認情況會走else邏輯。

而else的邏輯最終會調用doExecute方法:

protected void doExecute(Runnable task) { Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task)); thread.start();
}

 

 

我去,這不是每次都會創建一個新線程嗎?

 

沒錯,使用@Async注解開啟的異步功能,默認情況下,每次都會創建一個新線程。

如果在高并發的場景下,可能會產生大量的線程,從而導致OOM問題。

建議大家在@Async注解開啟的異步功能時,請別忘了定義一個線程池。

9. 自旋鎖浪費cpu資源

在并發編程中,自旋鎖想必大家都已經耳熟能詳了。

自旋鎖有個非常經典的使用場景就是:CAS(即比較和交換),它是一種無鎖化思想(說白了用了一個死循環),用來解決高并發場景下,更新數據的問題。

而atomic包下的很多類,比如:AtomicInteger、AtomicLong、AtomicBoolean等,都是用CAS實現的。

我們以AtomicInteger類為例,它的incrementAndGet沒有每次都給變量加1。

public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

它的底層就是用的自旋鎖實現的:

public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
}

在do...while死循環中,不停進行數據的比較和交換,如果一直失敗,則一直循環重試。

如果在高并發的情況下,compareAndSwapInt會很大概率失敗,因此導致了此處cpu不斷的自旋,這樣會嚴重浪費cpu資源。

那么,如果解決這個問題呢?

答:使用LockSupport類的parkNanos方法。

具體代碼如下:

private boolean compareAndSwapInt2(Object var1, long var2, int var4, int var5) { if(this.compareAndSwapInt(var1,var2,var4, var5)) { return true;
      } else { LockSupport.parkNanos(10); return false;
      }

當cas失敗之后,調用LockSupport類的parkNanos方法休眠一下,相當于調用了Thread.Sleep方法。這樣能夠有效的減少頻繁自旋導致cpu資源過度浪費的問題。

10. ThreadLocal用完沒清空

在java中保證線程安全的技術有很多,可以使用synchroized、Lock等關鍵字給代碼塊加鎖。

但是它們有個共同的特點,就是加鎖會對代碼的性能有一定的損耗。

其實,在jdk中還提供了另外一種思想即:用空間換時間。

沒錯,使用ThreadLocal類就是對這種思想的一種具體體現。

ThreadLocal為每個使用變量的線程提供了一個獨立的變量副本,這樣每一個線程都能獨立地改變自己的副本,而不會影響其它線程所對應的副本。

ThreadLocal的用法大致是這樣的:

(1) 先創建一個CurrentUser類,其中包含了ThreadLocal的邏輯。

public class CurrentUser { private static final ThreadLocal<UserInfo> THREA_LOCAL = new ThreadLocal(); public static void set(UserInfo userInfo) { THREA_LOCAL.set(userInfo);
    } public static UserInfo get() { THREA_LOCAL.get();
    } public static void remove() { THREA_LOCAL.remove();
    }
}

(2) 在業務代碼中調用CurrentUser類。

public void doSamething(UserDto userDto) { UserInfo userInfo = convert(userDto); CurrentUser.set(userInfo); ... //業務代碼 UserInfo userInfo = CurrentUser.get(); ... }

在業務代碼的第一行,將userInfo對象設置到CurrentUser,這樣在業務代碼中,就能通過CurrentUser.get()獲取到剛剛設置的userInfo對象。特別是對業務代碼調用層級比較深的情況,這種用法非常有用,可以減少很多不必要傳參。

但在高并發的場景下,這段代碼有問題,只往ThreadLocal存數據,數據用完之后并沒有及時清理。

ThreadLocal即使使用了WeakReference(弱引用)也可能會存在內存泄露問題,因為 entry對象中只把key(即threadLocal對象)設置成了弱引用,但是value值沒有。

那么,如何解決這個問題呢?

public void doSamething(UserDto userDto) { UserInfo userInfo = convert(userDto); try{ CurrentUser.set(userInfo); ... //業務代碼 UserInfo userInfo = CurrentUser.get(); ... } finally { CurrentUser.remove();
   }
}

需要在finally代碼塊中,調用remove方法清理沒用的數據。

原文地址:https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&mid=2247492962&idx=1&sn=17ed1d2ed950b4e9160218b296d19d4e

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 九九热在线视频观看这里只有精品 | 欧美一区黄色 | 美国av在线免费观看 | 国产噜噜噜噜噜久久久久久久久 | 91网视频| 久草在线视频新 | 日韩一级免费毛片 | av电影在线播放 | 91 免费视频 | 久久九九热re6这里有精品 | 欧美福利视频一区二区三区 | 成人短视频在线播放 | 艹逼| 欧美成人小视频 | 欧美在线a | 毛片免费在线观看视频 | 欧美a视频在线观看 | 深夜免费视频 | 亚洲婷婷日日综合婷婷噜噜噜 | 久久网站热最新地址 | 在线看免电影网站 | 成人午夜视频免费看 | 制服丝袜日日夜夜 | 欧美成人免费 | 久久精品亚洲一区 | 黄污视频在线看 | 毛片在线视频免费观看 | 羞羞视频免费网站 | 成人免费网站在线观看视频 | 国产婷婷一区二区三区 | 久久久久久久久久亚洲 | 狼伊千合综网中文 | 日韩在线欧美在线 | 久久久久se | 久久精品一区二区三区四区五区 | 日日鲁夜夜视频热线播放 | 日产精品久久久久久久 | 午夜精品网站 | 老师你怎么会在这第2季出现 | 男女生羞羞视频网站在线观看 | 久青草免费视频 |