synchronized (("" + userId).intern()) { // TODO:something }
JVM內存區域里面有一塊常量池,關於常量池的分配:
- JDK6的版本,常量池在持久代PermGen中分配
- JDK7的版本,常量池在堆Heap中分配
字符串是存儲在常量池中的,有兩種類型的字符串數據會存儲在常量池中:
- 編譯期就可以確定的字符串,即使用""引起來的字符串,比如String a = "123"、String b = "1" + B.getStringDataFromDB() + "2" + C.getStringDataFromDB()、這里的"123"、"1"、"2"都是編譯期間就可以確定的字符串,因此會放入常量池,而B.getStringDataFromDB()、C.getStringDataFromDB()這兩個數據由於編譯期間無法確定,因此它們是在堆上進行分配的
- 使用String的intern()方法操作的字符串,比如String b = B.getStringDataFromDB().intern(),盡管B.getStringDataFromDB()方法拿到的字符串是在堆上分配的,但是由於后面加入了intern(),因此B.getStringDataFromDB()方法的結果,會寫入常量池中
常量池中的String數據有一個特點:每次取數據的時候,如果常量池中有,直接拿常量池中的數據;如果常量池中沒有,將數據寫入常量池中並返回常量池中的數據。
這個在jdk6里問題不算大,因為String.intern()會在perm里產生空間,如果perm空間夠用的話,這個不會導致頻繁Full GC,
但是在jdk7里問題就大了,String.intern()會在heap里產生空間,而且還是老年代,如果對象一多就會導致Full GC時間超長!!!
慎用啊!解決辦法?終於找到了。
這里要引用強大的google-guava包,這個包不是一般的強大,是完全要把apache-commons*取締掉的節奏啊!!!
Interner<String> pool = Interners.newWeakInterner(); synchronized ( pool.intern("BizCode"+userId)){ //TODO:something }
該類對 intern 做了很多的優化,使用弱引用包裝了你傳入的字符串類型,所以,這樣就不會對內存造成較大的影響, 可以使用該類的 pool.intern(str) 來進行對字符串intern, 好了,這樣就解決了內存的問題了,那么我們使用了該優點,並且避免了內存占用問題,完美解決。但這種在分布式系統中會有問題
//類1- SynStringTest package com.tinygao.thread.synstring; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; @Slf4j public class SynStringTest { private final static SynString synStr = new SynString(); private final static Stopwatch sw = Stopwatch.createStarted(); private static BiConsumer<SynString, String> function = (x, y)->{ synchronized (x.getStringLock(y)) { log.info("Get lock: {}", y); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } }; public static void main(String[] args) throws InterruptedException { final ExecutorService executorService = Executors.newFixedThreadPool( 4, new ThreadFactoryBuilder().setNameFormat("SynString-%d").build() ); executorService.submit(()->{ doTask("test"); }); executorService.submit(()->{ doTask("test"); }); executorService.submit(()->{ doTask("test1"); }); executorService.shutdown(); executorService.awaitTermination(1, TimeUnit.DAYS); sw.stop(); } private static void doTask(String lockStr) { function.accept(synStr, lockStr); log.info("Do get lockStr successed waste time elapsed : {} ms", sw.elapsed(TimeUnit.MILLISECONDS)); } } //類2- SynString package com.tinygao.thread.synstring; import java.util.concurrent.ConcurrentMap; import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; @Slf4j public class SynString { private static ConcurrentMap<String,Object> parMap = Maps.newConcurrentMap(); public Object getStringLock(String string) { Object lock = this; if(parMap != null) { Object newLock = new Object(); lock = parMap.putIfAbsent(string, newLock); if(lock == null) { lock = newLock; } } return lock; } public static void main(String[] args) { Object result = parMap.putIfAbsent("h", "g"); log.info("Get result: {}", result); } }