2008年9月25日星期四

Java Biased Lock 实验

今天神舟七号升天,不知道是不是去成都的飞机都受影响,19:50的飞机改到23:20,在机场无聊,写篇博客吧

什么是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");
}
}

4 条评论:

James Zheng 说...

小强,在JDK5以上,System.nanoTime() 会比 System.currentTimeMillis() 准确很多 。它计算出的时间是以纳秒计(1ns = 1e-9s). 另外,在main thread中,是否因为测试需要才使用忙等待的. 如果不是,我建议把它改为线程同步方式。
public class BiasedLockTest {
    public static final long t1 = System.nanoTime();
    public static volatile long t2;
    static {
        System.out.println(t1 / 1e6);
    }
    public static void main(String[] args) {
        Object lock = new Object();
        TestThread[] threads = new TestThread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new TestThread(i, lock);
        }
        for (int i = 0; i < 100; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 100; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(t2 / 1e6);
        System.out.println((t2 - t1) / 1e6 + " ms used!");
    }
}
class TestThread extends Thread {
    private final Object lock;
    private final int id;
    public TestThread(int id, Object lock) {
        this.lock = lock;
        this.id = id;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            synchronized (lock) {
                int k = 0;
                k = k + 1;// dummy calculation
            }
        }
        BiasedLockTest.t2 = System.nanoTime();
        // System.out.println("Thread " + id + " is dead!");
    }
}

在JDK5以上的版本,还可以通过CountDownLatch这样方便的类来实现这类同步:

import java.util.concurrent.CountDownLatch;
public class BiasedLockTest {
    public static final long t1 = System.nanoTime();
    public static volatile long t2;
    static {
        System.out.println(t1 / 1e6);
    }
    public static void main(String[] args) {
        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();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t2 / 1e6);
        System.out.println((t2 - t1) / 1e6 + " ms used!");
    }
}
class TestThread extends Thread {
    private final Object lock;
    private final int id;
    private final CountDownLatch latch;
    public TestThread(int id, Object lock, CountDownLatch latch) {
        this.lock = lock;
        this.id = id;
        this.latch = latch;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            synchronized (lock) {
                int k = 0;
                k = k + 1;// dummy calculation
            }
        }
        BiasedLockTest.t2 = System.nanoTime();
        latch.countDown();
        // System.out.println("Thread " + id + " is dead!");
    }
}


我试着测试了一下这个虚拟机参数,在JDK1.5上没有发现显著的区别。

Unknown 说...

小胖, 那个latch不错,我按照你的建议改了一下代码
不过nanoTime意义不太大,我记得Win32SDK里面说Windows分时只能达到15ms的准确度,包括2.6的linux目前也达不到纳秒级别,不过JDK doc里建议用这个,以后硬件可以跟得上的话也行。
另外10e6=10^7 :-)

这个虚拟机参数只在JRE6上才有效果,JRE5上你尝试没有报错?

James Zheng 说...

小强,我查了一下官方文档,这个虚拟机参数在jdk1.5.06上加入的. JDK1.5默认是关闭的,所以在jdk1.5上有和没有这个参数差别挺大的,但是JDK6的测试中,这个参数默认应该是打开的,并且我感觉没有方法关闭它。

这个论坛上有个程序,我试了一下,在java5上面差别蛮大的。

http://forums.java.net/jive/thread.jspa?messageID=162813

public class TestEscapeAnalysis {
    private static final int COUNT = 100000000;
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 20; i++) {
            test();
        }
    }
    private static void test() {
        int x = 0;
        long ts = System.currentTimeMillis();
        Object lock = new Object();
        for (int i = 0; i < COUNT; i++) {
            synchronized (lock) {
                x++;
            }
        }
        long te = System.currentTimeMillis();
        System.out.println(x + ", time=" + (te - ts) / 1.0);
    }
}

测试结果:无参数

100000000, time=5078.0
100000000, time=4282.0
100000000, time=4687.0
100000000, time=4359.0
100000000, time=4110.0
100000000, time=4094.0
100000000, time=3796.0
100000000, time=4469.0
100000000, time=4828.0
100000000, time=4188.0
100000000, time=3969.0
100000000, time=4390.0
100000000, time=4375.0
100000000, time=4360.0
100000000, time=4531.0
100000000, time=4375.0
100000000, time=4922.0
100000000, time=3968.0
100000000, time=4407.0
100000000, time=4375.0

-XX:+UseBiasedLocking

100000000, time=5296.0
100000000, time=579.0
100000000, time=578.0
100000000, time=547.0
100000000, time=453.0
100000000, time=484.0
100000000, time=578.0
100000000, time=485.0
100000000, time=578.0
100000000, time=562.0
100000000, time=406.0
100000000, time=484.0
100000000, time=469.0
100000000, time=453.0
100000000, time=578.0
100000000, time=563.0
100000000, time=406.0
100000000, time=547.0
100000000, time=562.0
100000000, time=485.0

Unknown 说...

我只知道这个是JRE6先加入的,然后back port到1.5的某个update里的,照你这么说,应该差不多
我觉得这个优化不同于biased lock,对于biased lock如果不能关闭那么会导致一个程序在不同JRE上非常大的行为差别
而这个优化是安全的,不会有任何副作用