Java中String做为synchronized同步锁
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); } }