并发环境下延迟加载Singleton实例的终极方案:Initialization-on-demand holder idiom

相信你对这个问题已经很熟悉了:并发环境下如何延迟加载Singleton Instance ? public class Expensive { private static Expensive instance; public static Expensive getInstance() { if (instance == null) { instance = new Expensive(); } return instance; } } 如果getInstance()处不使用synchronzied, 可能导致产生两个singleton对象, 或者拿到半残的instance对象。至于臭名昭著的DCL,那就更不必说了。 如果用synchronized, 那可能因为锁的原因在高并发下使性能受损。 最后一招似乎是不使用延迟加载,而是在类初始化时主动生成instance对象; 但是,如果生成这个对象确实很昂贵,而且又很有可能确实用不上它,那主动初始化岂不是很浪费? 《Java并发编程实践》给出了致命一招:Initialization-on-demand holder,即 把instance的初始化投入到一个内部类的初始过程中,就可以兼顾正确性和性能。   1. 内部类的初始化是延迟的,外部类初始化时不会初始化内部类。   2. 内部类的初始化是线程安全的,所以不用担心两个instance或半残instance的问题。   3. 第一次获取instance需要等它初始化,以后再获取就不必了,而且也不需要锁。所以在性能上也是妥妥的。 public class Expensive { private static class LazyHolder …

并发环境下延迟加载Singleton实例的终极方案:Initialization-on-demand holder idiom Read More »

如无必要,尽量不要用ReentrantLock

《JAVA并发编程》这本书说:如无必要,尽量不要用ReentrantLock. 用synchronized关键字就可以了。 因为使用ReentrantLock时必须要记得在finally块里使用unlock(),一旦忘了(发生这种事的概率不低),可能造成很大的悲剧。 ReentrantLock和synchronized的语义是一样的,在JAVA 5里前者性能比后者好很多,但在JAVA 6里两者已经没什么区别了,所以用synchronized不会有性能问题。 ReentrantLock的唯一优势在于它提供了无阻塞加锁、可中断加锁等特性,这对避免死锁有很大帮助。如果你确实需要这些特性,才使用ReentrantLock.

shut down hook示例

package player.kent.chen.learn.sdh; public class HelloShutdownHook { public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { System.out.println(Thread.currentThread().getName() + "-钩子,再见1"); } }); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { System.out.println(Thread.currentThread().getName() + "-钩子,再见2"); } }); System.out.println(Thread.currentThread().getName() + "-Hello, world!"); } } 输出: 引用 main-Hello, world! Thread-1-钩子,再见2 Thread-0-钩子,再见1 可见:   1. 系统退出时会执行hook里面的操作   2. 这些操作的执行是在独立线程里执行的,而且是异步的 …

shut down hook示例 Read More »

例示CompletionService的使用

它跟普通的Executor + Callable + Future 没什么本质区别。只不过当有多个任务需要提交时,自己手动维护一堆Future、并依次地调用future.get() 会很繁琐。 CompletionService使这个变得简单很多。

活锁(livelock)

活锁(livelock): 没有真正的锁问题,而是进程不停地执行重复的操作,却总是失败。 《JAVA并发编程实践》给出了两个例子: 1. MQ消费者处理消息失败后将消息丢回到队列头部,然后立即马上收到这个消息,然后再处理失败入。。。 2. 两个礼貌的人在路上挡住彼此去路,然后同时往一边让,结果还是互相挡住;然后在同时往另一边让,结果也是互相挡住;然后再往一边让。。。 解决活锁的办法是在操作中引入退出机制,或者引入随机性。

避免死锁的一招: 按同样的顺序加锁

当进程A正在把钱从甲账户转到乙账户时,进程B也正在把钱从乙账户到甲账户;转账时需要锁住账户,如果A锁住甲等待乙时,B已锁住乙并等待甲,那么双方就会陷入死锁。 要避免这种死锁,有一个办法是: 按同样的顺序加锁。 注意,读书不要读得太快。 听说“按同样顺序加锁”就能避免死锁,就总是先锁出账者,再锁入账者。。。 结果,还是死锁。 “按同样的顺序加锁”不是指“两个进程使用同样的操作顺序(比如先锁出账者,再锁入账者)”,而是要“对给定的两个资源,所有进程总是先锁资源1,再锁资源2”。 比如,在数据库中,可以总是先锁ID更大的那条记录;在JAVA代码中,可以总是先锁hashcode更大的那条记录。

JAVA代码片断:获取当前进程的PID

private static String getPid() throws IOException { Process p = Runtime.getRuntime().exec("/home/kent/opt/jdk1.6.0_41/bin/jps"); InputStream in = p.getInputStream(); List<String> jpsLines = IOUtils.readLines(in); IOUtils.closeQuietly(in); for (String line : jpsLines) { if (line.contains(HelloPoolSize.class.getSimpleName())) { return line.split("\\s")[0]; } } throw new IllegalStateException("拿不到pid"); }