2011年4月28日星期四
A quick map access benchmark
Single thread
Erlang ETS 3+ seconds
Java HashMap 0.18 seconds
Java ConcurrentHashMap 0.41 seconds
High-scale lib NonBlockingHashMap 0.43 seconds
1000 threads
Erlang ETS 2+ seconds
Java HashMap N/A (not thread-safe)
Java ConcurrentHashMap 0.56 seconds
High-scale lib NonBlockingHashMap 0.51 seconds
2011年4月13日星期三
json java libraries benchmark - jsonlib, jackson, gson
Because I don't parse huge json string so I only test object binding which is much more convenient. For the same reason, I did not have jettison or gson-streaming in this test because they are much faster with the trade-off of lower level of encapsulation and OOP.
For gson and jackson, the instance of Gson and ObjectMapper can be reused. Jackson performs very good because its ObjectMapper caches class mapping meta data. Jackson will be slow if you don't reuse ObjectMapper, it's designed for reuse.
The scala built-in parser is extremely slow (100 X slower!) and lift-json is the defacto lib for scala, which is about 15% slower than jackson.
| - | Bean to JSON | JSON to Bean |
| Jsonlib 2.4 | 2058 | 3055 |
| Gson 1.7 | 1481 | 1472 |
| Jackson 1.7.6 | 694 | 667 |
Test env: jdk6 on Mac OS X 10.6.7
unit: millseconds
Conclusion: Jackson is the best solution for server side because of its cache. Another win of jackson is that it has zero dependency, jsonlib sucks a lot in both performance and dependency. Gson is also very fast with zero dependency, without cache, it's ad-hoc usage is the best, it's best for less heavy use of json, like android device or web start applet (170K only).
2009年1月18日星期日
用 PhantomReference 避免OutOfMemory
PhantomReference比Weak/Soft的引用强度都要低,PhantomReference.get()总是返回null,为什么呢?
其实PhantomReference而要是不可缺少的重要引用类型, 我们知道WeakReference在finalize方法调用或被gc清理之前会进入ReferenceQueue, 这时候没有任何strong reference引用对象,然后可以通过ReferenceQueue去做收尾工作。但是在finalize()方法,或者重新给这个对象一个引用使它reachable(从某个活动线程调用栈,或者静态变量可达), 使这个对象延长生命周期暂时不会被gc清理。而PhantomReference只会在对象被从内存中清除后才会进入队列,get()总是返回null (ReferenceQueue通知一个PhantomReference的时候,既然内存都已经物理释放了,当然也无法给你一个对象,所以get总是返回null也有这个原因), 所以你没有办法重新使这个对象再次reachable。PhantomReference只是提供了一种方式让你跟踪一个曾经产生过的对象,由此让你知道这个对象到底有没有被物理的清除。
比如当你的applet程序需要处理一个非常大的图片的时候,你可能希望图片处理结束并且内存被释放之后再处理下一个图片。如果用PhantomReference来引用上一个图片对象,当ReferenceQueue通知你的时候,你就可以知道上一个内存对象已经被物理清除,你可以继续下一个大内存对象的处理了。这样就可以避免由于GC线程优先级低导致上一个大内存对象还没有释放下一个大内存对象又被创建,让OutOfMemoryError出现的概率低一些。还有一个好处,就是PhantomReference比用finalize方法好的多,因为VM对finalize的处理不如PhantomReference简单可靠,只不过是你要写的代码稍微多一点点而已。
2008年12月27日星期六
Hibernate 的乐观锁@Version和 MySQL 的两个小问题
MySQL 6 才会支持毫秒,所以目前你只能用INT或者BIGINT来保存Version了。
但是就算是你用了INT你可能仍然遇到别的错误
Caused by: java.lang.NullPointerException
at org.hibernate.type.IntegerType.next(IntegerType.java:59)
at org.hibernate.engine.Versioning.increment(Versioning.java:108)
at org.hibernate.event.def.DefaultFlushEntityEventListener.getNextVersion(DefaultFlushEntityEventListener.java:365)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:257)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:128)
这个错误一看就头大了吧,Hibernate的源代码是这样的
public Object next(Object current, SessionImplementor session) {
return new Integer( ( (Integer) current ).intValue() + 1 );
}
初步分析原因是前面事件通知的时候传入的当前version是空的,具体看这里 http://opensource.atlassian.com/projects/hibernate/browse/HHH-3030
最简单的解决办法是给@version字段在数据库里设置一个默认值,比如0
Opensource意味着你不能发现bug的时候打电话对售后大喊大叫,没办法,要么自己写一个hotfix,要么等官方fix吧。
2008年11月27日星期四
Swing 的 Validate, Invalidate, Revalidate
Class Hierarchy
Component.invalidate():
Invalidates this component. This component and all parents above it are marked as needing to be laid out.
Component.validate()
Ensures that this component has a valid layout. This method is primarily intended to operate on instances of Container
Container.validate()
Validates this container and all of its subcomponents.
The validate method is used to cause a container to lay out its subcomponents again. It should be invoked when this container's subcomponents are modified (added to or removed from the container, or layout-related information changed) after the container has been displayed.
Set valid=true and call Container.validateTree().
Container.validateTree() will call all its sub containers’ validateTree() recursively, so all sub components’ Component.validate() method will be called.
Container.validateTree()
Container.invalidate()
Basically do the same thing as super class Component.invalidate except for notifying the layout manager the changes, since the major difference between Component and Container is that Container can contains objects with layout manager.
LayoutManager2.invalidate() will discard the cached size information about the layout, so next time the layout will be re-calculated.
Conclusion on validate/invalidate
Invalidate() causes the component hierarchy to be marked as needing to be laid out again, and the validate() causes that to be done. It may be expensive, but is a way of getting the peers to recalculate size and to do what is needed to bring the display up to date. It has limitations: it doesn't cause an immediate screen update when invoked from an event handler, where paintImmediately() does.
JComponent.revalidate()
Supports deferred automatic layout.
Calls invalidate and then adds this component's validateRoot to a list of components that need to be validated. Validation will occur after all currently pending events have been dispatched. In other words after this method is called, the first validateRoot (if any) found when walking up the containment hierarchy of this component will be validated. By default, JRootPane, JScrollPane, and JTextField return true from isValidateRoot.
Conclusion on revalidate
This method will automatically be called on this component when a property value changes such that size, location, or internal layout of this component has been affected. This automatic updating differs from the AWT because programs generally no longer need to invoke validate to get the contents of the GUI to update. Because RepaintManger.addInvalidComponent() will validate them.
2008年10月5日星期日
Concurrency Cheat Sheet
All concurrency issues boil down to coordinating access to mutable state. The less mutable state, the easier it is to ensure thread safety.
Make fields final unless they need to be mutable.
Immutable objects are automatically thread-safe.
Immutable objects simplify concurrent programming tremendously. They are simpler and safer, and can be shared freely without locking or defensive copying.
Encapsulation makes it practical to manage the complexity.
Guard each mutable variable with a lock.
Guard all variables in an invariant with the same lock.
Hold locks for the duration of compound actions.
A program that accesses a mutable variable from multiple threads without synchronization is a broken program.
Don't rely on clever reasoning about why you don't need to synchronize.
Include thread safety in the design processor explicitly document that your class is not thread-safe.
Document your synchronization policy.
2008年10月4日星期六
一个随便写写的测试Java reordering的程序
class Obj {
int x;
public Obj(int x) {
this.x = x;
}
}
这个类总是被一个线程传入参数100不停的构造,另外一个线程读取,理论上另一个线程可能只会看到x=100对么?其实不是,如果java -server来执行这个程序你会看到很多x=0的情况出现。为什么呢?就是因为reordering的优化
如果你给x加上volatile或者final关键字你就不会看到这个问题了,或者给obj加上volatile也可以。
因为这两个关键字都禁止reordering,在JDK1.4里或许不行,但是JDK5重新规定了volatile的行为,其中包括了不仅和volatile变量之间禁止reordering,也和non-volatile变量之间禁止reordering了。
final这里可能更好一些,因为Obj这样就是一个immutable对象了,而我们Obj的构造函数并没有escape this到外部线程,所以可以获得JDK5的所谓Initialization Safety的特性,保证了final字段x一定是不需要同步就可以被外部线程看到,这蕴含着禁止reordering.
package test.jmm;
public class TestReordering {
public Obj obj=null;
public static void main(String[] args) {
TestReordering test = new TestReordering();
new WriterThread(test).start();
new ReaderThread(test).start();
}
}
class Obj {
int x;
public Obj(int x) {
this.x = x;
}
}
class WriterThread extends Thread {
private TestReordering test;
public WriterThread(TestReordering test) {
this.test = test;
}
public void run() {
while(true) {
test.obj = new Obj(100);
}
}
}
class ReaderThread extends Thread {
private TestReordering test;
public ReaderThread(TestReordering test) {
this.test = test;
}
public void run() {
while(true) {
if (test.obj != null)
if(test.obj.x == 0) {
System.out.println("found");
} else {
//System.out.println(test.obj.x);
}
}
}
}
2008年10月2日星期四
Strictfp到底有多实用?
如果满足这个IEEE754是否表示我的程序可以移植?我觉得如果你的程序移植性依赖于浮点运算的结果本身就是个错误,所以这个问题似乎没有太大意义。
如果我真的需要非常严格的数值,我可能会用BigDecimal
所以,有多大用处呢?貌似很多5年Java经验的人也不太清楚strictfp。
浅谈JMM的reordering导致的一些问题
什么是reordering问题?
表示对代码指令的执行顺序的改变。比如先写a再写b,如果b和a不是互相依赖,编译器可以改成先写a和另外一个变量,凑成一个字长写回,这种优化不仅可能发生在编译期间,甚至发生在运行期。这些看起来是很聪明的优化,但是这些优化大多数只是对单线程的执行优化,在多线程的时候会导致问题。过去C是依赖于线程类库来保证内存操作的正确性,Java是通过JMM实现的。
recording可以发生在几种情况如下:
*编译期间,编译器可以对某些指令重新排序甚至改变来提高性能,比如inline
*运行期间,处理器在某些情况下可能会改变指令的运算顺序来提高性能,比如64位子长机器为了一次写两个32位的值
*运行期间,缓存(寄存器)有可能不是按照指令的顺序写回更新
比如Doug Lea的一个例子:
Class Reordering {
int x = 0, y = 0;
public void writer() {
x = 1;
y = 2;
}
public void reader() {
int r1 = y;
int r2 = x;
}
}
一个线程调用writer的时候如果处理器发生了reordering,可能会先执行y=2然后再执行x=1。如果这时候另外一个线程调用reader,y被读取的时候可以看到2但是不一定能看到x=1,很可能看到x=0,因为writer执行的顺序发生了变化.
另外,对于比较短小的函数,编译器会做inline,和C++里inline关键字含义很类似,这样可以减少方法调用栈的频繁压栈弹栈来提升性能。比如这样一个构造函数
public Foo {
public int x = 0;
public Foo(int v) {
x=v;
}
}
当编译Foo f = new Foo()的时候,编译器会inline成两行普通代码:
Foo f = 分配内存;
f.x=0;
f.x=v;
这样导致一个问题就是如果另外一个线程看到x的时候可能是0,稍后又变成v.这会导致另外一个线程看到了一个不完全构造的Foo对象!即便你在构造函数内没有写类似于global.ref=this,即便你没有escape this reference到外部线程,由于这种inline+reordering的优化,外部线程仍然可以看到半成品的对象。这对于一般的情况我们可能会考虑把x作为私有的不让外部线程访问,或者其他线程访问的时候同步,因为同步会除了mutex和flush还会设立reordering的上下边界,也就是说在同步边界上,另外一个线程不会先获得s2的引用,必须退出同步块,s2完全构造结束后另一个线程才能得到s2的引用。但是对于我们通常理解final应该不受这个约束的,事实上老的JMM不是这样处理的,这对于final的字段是一场灾难。
final问题:
过去final没有被特殊处理,这导致final的东西不final,会发生很困惑不可预料的问题。
比如String这个类,包含了三个final成员变量,分别是长度,偏移量和一个字符数组。
下面的两行代码如果被一个线程执行到第二行的时候,
String s1 = "/usr/tmp";
String s2 = s1.substring(4);
JVM为s1.substring()所将要产生的String调用parent constructor Object(),这时候heap分配Object所需要的内存并把地址赋值给s2,然后所有final的成员变量会被初始化为0,然后String的构造函数再把offset和length设置为期望的值。由于在老的JMM里在内存地址先复制给s2然后再调用String的构造函数就会导致另外一个线程如果这时候使用了s2就会看到offset=0,也就是说s2="/usr/tmp",稍后再一次使用s2又会看到s2="/tmp"
这样感觉就是final的字段也会变化,immutable的String也会变化,这个问题会非常难以发现。JSR133新的内存模型改变了这种行为,叫做initialization safety. 意思是说当一个对象构造过程中没有把自己通过引用暴露给其他线程,构造函数结束后,这个对象才算安全构造成功,之后这个对象的内部所有的final字段都可以不需要同步就可以让外部的线程看到。这也隐含着新的JMM对s2的赋值会发生在构造函数完全结束,如果有final字段的话。
volatile问题
一般来说普通变量会被保存到线程级别local copy,通常是寄存器,用的时候直接从速度最快的寄存器读取,避免多次频繁的寻址读取主存,这就意味着在某些情况下必须在主存和local copy之间flush.这种flush保证了同步,保证了happens before 原则。
而volatile他会总是访问主存而不是local copy,换句话说运算的时候,每次都是重新读取内存然后写入寄存器再参与计算,L1/L2 cache也不会缓存。
这样他总是可以看到其他线程对主存的最新的操作结果。但是这不能保证独享,因为没有lock
比如两个线程都进行a++,那么理路上可能两个线程都看到了初始值0,然后都累加到1,都在某个flush的时候写回主存,结果是最终结果不是2,而是1。所以这个不可以用于计数器或者信号量。
但是他更快更简单,不需要锁,但是如果你有大量的volatile变量参与运算,那么大量的main memory操作也可能会比较慢,这时候你也可以考虑用lock批量的操作之后flush回main memory。
老的JMM中volatile是仅仅不可以和volatile变量之间reordering,但和普通变量之间是可以发生reordering的,这导致实用性大大下降。Doug Lea给了一个例子:
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
如果在老的JMM中,x和v可以reorder,处理器可能先写了v=true,这个时候还没有写x=42,恰巧这个时候另外一个线程执行reader(),那么这个线程可以看到v=true,然后认为x一定已经是42了,其实不然,这时候x是0!只有在新的JMM中,volatile和non-volatile彼此不允许在一个方法调用栈中recorder的时候才能保证数据操作的顺序性。注意:volatile还保证原子性。对于long和double由于是64位,其他primitives类型是32位,所以在32位字长的系统上long和double不能保证原子性,这时候需要volatile.对于64为系统是否需要volatile我不敢说,但是我会安全起见也加上volatile,毕竟Java会运行在不同的硬件平台上。
一个相关的有趣的问题可以在这里找到,volatile禁止reordering最终解决了DCL问题。
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
这里我只是简单介绍一下:
下面这段代码想避免每次都要同步的开销同时保证只创建一次helper.
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
其实这里有个问题,helper的构造可能会被Inline+reorder,导致赋值和构造顺序的变化,所以你是没有办法保证得到一个完全构造好的helper的,除非helper所有的字段都是final的,而且运行在JDK5下。有了volatile可以禁止这种优化,下面的代码就可以正常运行:
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}
当然这里注意的是对于32位的primitive,不加volatile的DCL也是可以的,因为32位字长的机器写32位的int/float本身就是硬件提供了原子操作的能力的,但是32位子长的机器写64位的long和double的时候,也一定要加volatile,因为第一个进入的线程写高32位和低32位之间,如果另外一个线程进入同步快会看到helper不等于0(其实是写了一半的数值),第二个线程就会面临一场灾难了.
volatile还有一些技巧,比如当作cheap lock用:
public class CheesyCounter {
private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
}
小结,JMM其实更为复杂的多,写出伸缩性好的程序一定要对JMM有基本的理解,因为JMM其实主要是和线程相关的,而大规模的应用几乎没有不使用线程的,而且可能是非常intensive的使用。
2008年9月25日星期四
escape analysis 和 lock elision
比如最重要的一个就是锁擦除 lock elision.
如果escape analysis发觉这个引用只在一个本地作用域(局部变量),这表示只有在当前作用域的线程才有可能访问这个锁,在这种情况下永远不可能有别的线程进入这个作用域和当前线程竞争,所以这种情况下这个锁可以被消除,这就是lock elision.
比如这段代码:
public String concatBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
这种情况下StringBuffer对象的作用域就这这个方法内,所以StringBuffer完全没有必要线程安全加锁(当然更好的做法是用StringBuilder,不要依赖于聪明但是有时候行为不可预料的编译器和JIT/JRE.), 所以escape analysis结果表示可以优化,lock elision这种情况下就可以发生。
以下是测试程序
package test.thread;
public class LockElisionTest {
public static void main(String[] args) {
long t1 = System.currentTimeMillis();
LockElisionTest obj = new LockElisionTest();
for (int i = 0; i < 10000000; i++) {
obj.concatBuffer("1", "2");
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
public String concatBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
}
注意的是是-XX:+DoEscapeAnalysis默认没有打开,而且你需要用-server模式
结果JRE5没有打开escape analysis是3500毫秒
JRE6打开是2100毫秒,关闭2200毫秒左右(估计因为还有别的优化)
Java Biased Lock 实验
什么是biased lock?
Biased Locking is a class of optimizations that improves uncontended synchronization performance by eliminating atomic operations associated with the Java language’s synchronization primitives.
有些时候一个线程多次获得对象锁的操作中,理论上这些操作可以合并在一起,而减少lock/unlock的时间。不过这样会导致对其他线程的不公平,所以叫biased lock.通常是对第一个获得锁的线程偏心。
禁止:-XX:-UseBiasedLocking
使用:JRE6默认使用,或者显式使用-XX:+UseBiasedLocking
以下是我的测试程序,在JRE6上使用biased lock只需要500毫秒左右,而使用JRE5则需要5500毫秒左右,快10倍以上。
奇怪的是JRE6上禁止biased lock之后貌似性能没有什么变化,如果你打印一些调试信息(关闭注释System.out.println("T" + id + " holds lock")那行)你会发现仍然是biased lock,知道一个线程结束才释放锁。
package test.thread;
import java.util.concurrent.CountDownLatch;
public class BiasedLockTest {
public static volatile Long t1 = System.nanoTime();
static {
System.out.println(t1);
}
public static volatile Long t2;
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
TestThread[] threads = new TestThread[100];
CountDownLatch latch = new CountDownLatch(threads.length);
for (int i = 0; i < 100; i++) {
threads[i] = new TestThread(i, lock, latch);
}
for (int i = 0; i < 100; i++) {
threads[i].start();
}
for (int i = 0; i < 100; i++) {
threads[i].join();
}
System.out.println(t2);
System.out.println( (t2 - t1) / 10e5);
}
}
class TestThread extends Thread {
final private int id;
final private Object lock;
final private CountDownLatch latch;
public TestThread(int id, Object lock, CountDownLatch latch) {
this.id = id;
this.lock = lock;
this.latch = latch;
}
public void run() {
for(int i = 0; i < 10000; i++) {
synchronized (lock) {
int k = 0;
k=k+1;//dummy calculation
}
// System.out.println("T" + id + " holds lock");
}
BiasedLockTest.t2 = System.nanoTime();
latch.countDown();
// System.out.println("T" + id + " dies");
}
}