Monthly Archives: March 2013

SecurityManager一般不起作用

在普通webapp、或java application中,如果没有特别指定,System.getSecurityManager()的返回值都是空的,也就是说拿不到SecurityManager,也不会用它去做任何检查。

所以,在这些程序中通过反射直接访问一个对象的私有变量的值,是可以得手的。 有些框架也直接利用这个默认设置来耍流氓,比如Spring的@Resource实现。

但在applet、java web start等环境中SecurityManager默认是有的,这时直接通过反射访问私有变量就会失败。

HashSet和TreeSet如何确定元素的唯一性?

往Set里先后加入两个“有点相像”的对象,最后Set里会有一个对象还是两个?

实验结论如下:

HashSet

两个对象是否彼此equals ? 两个对象hashCode是否相等 最终Set大小 备注
true true 1
true false 2 有点违反Set接口契约
false true 2
false false 2

TreeSet

TreeSet在判断唯一性时不考虑hash code; 从下面的结果你也可以发现,它连equals()都不理会。

两个对象是否彼此equals ? 两个对象compare后是否相等 最终Set大小 备注
true true 1
true false 2
false true 1 违背了Set接口的契约,令人吃惊?
false false 2

HashMap如何应对hash collision ?

HashMap如何应对hash collision?  只通过key的hashCode()来区分key是不够的,因为两个不同key的hash code很容易就重合了。

当hash-code重合时,区分的办法就是 ==和equals()方法了;如果两个key的hashCode()相同而但equals()和==都不同,则将这两个key散列到同一个桶上,但不互相覆盖。

为求明白,可以总结一下equals(), ==, hashCode()呈现不同结果时,HashMap的不同行为:

两个key的hashCode()相等? 两个key是同一个对象? 两个key的equals() == true? 是否导致覆盖 备注
true true true
true true false N/A
true false true
true false false
false true true 单个key对象存入HashMap后,hashCode发生修改,然后再存一次
false true false N/A
false false true
false false false

并发环境下延迟加载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 {
        private static final Expensive instance = new Expensive();
    }

    public static Expensive getInstance() {
        return LazyHolder.instance;
    }
}

更多介绍:
http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

如无必要,尽量不要用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. 这些操作的执行是在独立线程里执行的,而且是异步的

 

例示CompletionService的使用

package player.kent.chen.learn.completionservice;

import java.io.File;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.FileUtils;

public class HelloCompletionService {

    public static void main(String[] args) throws InterruptedException {
        //生成CompletionService实例
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CompletionService<String> completionService = new ExecutorCompletionService<String>(
                executor);

        //定义任务并置入CompletionService任务池
        for (int i = 0; i < 4; i++) {
            final String fn = String.valueOf(i) + ".txt";
            Callable<String> task = new Callable<String>() {
                public String call() throws Exception {
                    return FileUtils.readFileToString(new File("/home/kent/temp/" + fn));
                }
            };
            completionService.submit(task);
        }

        for (int i = 0; i < 4; i++) {
            Future<String> future = null;
            try {
                //至少有一个任务完成了,take()才能拿到东西; 所以这里的future其实是done==true的future
                //这也正是CompletionService的作用:任务一旦完成,就可以被取到
                future = completionService.take();
                String text = future.get();
                System.out.println(text);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                future.cancel(true);
                return;
            } catch (ExecutionException e) {
                e.printStackTrace(); //真正的应用中应该注意一下此处的处理
            }
        }

        executor.shutdown();
        executor.awaitTermination(1000l, TimeUnit.SECONDS);

    }
}

活锁(livelock)

活锁(livelock): 没有真正的锁问题,而是进程不停地执行重复的操作,却总是失败。

《JAVA并发编程实践》给出了两个例子:

1. MQ消费者处理消息失败后将消息丢回到队列头部,然后立即马上收到这个消息,然后再处理失败入。。。

2. 两个礼貌的人在路上挡住彼此去路,然后同时往一边让,结果还是互相挡住;然后在同时往另一边让,结果也是互相挡住;然后再往一边让。。。

解决活锁的办法是在操作中引入退出机制,或者引入随机性。