Interners详解
关于String
大家都知道,String是final的,每次对它的操作都会产生新的String,这很大程度上是安全性的考虑,但是产生大量的String也是会有一些问题的,1.大量的String会对gc产生影响;2.两次 new String(“aa”)操作,产生的String不一样,如果用这两个去做synchronized(String)操作就达不到想要的效果,因为synchronized必须是对同一个对象进行加锁才有效果。
有两种办法可以让两次 new String(“aa”)的String指向同一对象:java的intern方法和guava Interners。
我们先来看一个不使用intern方法和Interners的例子:
public class InternsTest { public static void main(String[] args) { Interner<String> pool = Interners.newWeakInterner(); for (int i = 0; i < 5; i++) { String lock=new String("lock"); TestInterns testInterns = new TestInterns(pool,lock, i); Thread thread = new Thread(testInterns); thread.start(); } } } class TestInterns implements Runnable { private String lock; private int out; private Interner<String> pool; public TestInterns(Interner<String> pool,String lock, int out) { this.lock = lock; this.out = out; this.pool=pool; } @Override public void run() { //重点 synchronized (lock) { System.out.println("--"+out); System.out.println(out+lock); System.out.println(); } } }
结果:
--0
--1
0lock
--2
2lock
1lock
--3
3lock
--4
4lock
可以看到,多线程环境下,执行结果出现了不一致,说明 synchronized (lock)没有达到理想的效果,这是因为每次的lock都是不一样的。
下边看一个使用了intern方法的例子:
将上边代码synchronized (lock) 换成synchronized (lock.intern()),这样的话,每次拿到的都是从常量池中拿到的lock引用,所以起到了对lock加锁的作用
结果:
--0
0lock
--1
1lock
--2
2lock
--3
3lock
--4
4lock
下边看一个使用了Interners的例子:
将上边代码synchronized (lock) 换成synchronized (pool.intern(lock))
结果:
--0
0lock
--1
1lock
--2
2lock
--3
3lock
--4
4lock
可以看到,也起到了对lock加锁的作用。
总结:
1.在这里,intern方法和Interners都能达到对lock加锁的效果,只是区别是:(1)interns常量池有限,存储在hashtable中,数据多了之后,碰撞厉害,而且容易加重full gc负担 (2)Interners内部基于ConcurrentHashMap实现,而且可以设置引用类型,不会加重full gc负担,但有一个问题就是如果gc回收了存储在Interners里面的String,那么pool.intern(lock)可能也会返回不同的引用,总之,还是建议使用Interners,效率和内存使用率更高
2.关于对String进行加锁的场景,大家可以想象一下,上边的代码是这样一个场景:lock是一个订单号,这个订单号比较特殊,对这个订单相关的操作必须串行,其他的订单可以并行执行。在多线程情况下,我们就需要对这个特殊的订单号进行加锁,以保证对这个订单操作的串行化,但是我们每一个进来的请求(线程)肯定带的订单号(lock)都是不一样的对象,如果贸然synchronized(lock)操作,肯定就会出现上边第一种情况的问题,所以这个时候Interners和intern方法就有用了。