《Java中Lock和synchronized的比较和应用》

《Java中Lock和synchronized的比较和应用》

尽管synchronized在语法上已经足够简单了,在JDK 5之前只能借助此实现,但是由于是独占锁,性能却不高,因此JDK 5以后就开始借助于JNI来完成更高级的锁实现。

JDK 5中的锁是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一对可供读写并发的锁。我们从java.util.concurrent.locks.Lock的使用,可以查询API文档,这里不再说明。

java.util.concurrent.locks.Lock类,既然是锁,肯定有用于线程获取锁和释放锁的方法存在,这两个方法为:

1、void lock();

函数功能:获取锁。如果锁不可用,由于线程调度目的,将禁用此线程,并且在获得锁之前,该线程将一直处于休眠状态。

2、unlock();

函数功能:释放锁。对应于所有的获取锁,例如lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的话应该对应着一个unlock(),这样可以避免死锁或者资源浪费。

使用Lock同步来模拟AtomicInteger类

我们知道AtomicInteger类是一个int型原子操作类。下面我们就使用Lock类来模拟实现一个AtomicInteger。

在模拟实现之前,我们如果不太了解AtomicInteger,可以先看下这个类的API文档以及AtomicInteger源码实现。

AtomicInteger的所有原子操作都依赖于sun.misc.Unsafe类,Unsafe类中相关操作都是对应于一条与平台有关的处理器CAS指令。

使用锁Lock模拟的AtomicInteger类代码如下:

类代码虽长,但思路相当简单。

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    public class AtomicIntegerLock {

        private volatile int value;

        private Lock lock = new ReentrantLock();

        public AtomicIntegerLock(int value) {
            this.value = value;
        }

        public void set(int newValue){
            lock.lock();
            try{
                this.value = newValue;
            }finally{
                lock.unlock();
            }
        }

        public final int get(){
            lock.lock();
            try{
                return value;
            }finally{
                lock.unlock();
            }
        }

        public final int getAndSet(int newValue){
            lock.lock();
            try{
                int oldValue = value;
                value = newValue;
                return oldValue;
            }finally{
                lock.unlock();
            }
        }

        public final int getAndAdd(int delta){
            lock.lock();
            try{
                int oldValue = value;
                value+=delta;
                return oldValue;
            }finally{
                lock.unlock();
            }
        }

        public final int addAndGet(int delta){
            lock.lock();
            try{
                value+=delta;
                return value;
            }finally{
                lock.unlock();
            }
        }

        public final boolean getAndCompare(int expect,int newValue){
            lock.lock();
            try{
                if(this.value == expect){
                    value = newValue;
                    return true;
                }
                else{
                    return false;
                }
            }finally{
                lock.unlock();
            }
        }

        public final int getAndIncrement(){
            lock.lock();
            try{
                return value++;
            }finally{
                lock.unlock();
            }
        }

        public final int getAndDecrement(){
            lock.lock();
            try{
                return value--;
            }finally{
                lock.unlock();
            }
        }

        public final int incrementAndGet(){
            lock.lock();
            try{
                return ++value;
            }finally{
                lock.unlock();
            }
        }

        public final int decrementAndGet(){
            lock.lock();
            try{
                return --value;
            }finally{
                lock.unlock();
            }
        }

        public final String  toString(){
            return Integer.toString(get());
        }

    }

Lock同步和synchronized同步两种锁的性能

下面我们使用synchronized和Lock分别进行同步的性能比较:分别开启10个线程,每个线程计数到1000000,统计两种锁同步所花费的时间

    public class TestAtomicIntegerLock {

        private static int synValue = 0;
        public static void main(String[] args) {
            int threadNum = 10;
            int maxValue = 1000000;

            Thread[] t = new Thread[threadNum];
            Long begin = System.nanoTime();
            for(int i=0;inew AtomicIntegerLock(0);
                t[i]=new Thread(new Runnable(){

                    @Override
                    public void run() {
                        for(int j=0;jfor(int i=0;i//main线程等待前面开启的所有线程结束
            for(int i=0;itry {
                    t[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("使用lock所花费的时间为:"+(System.nanoTime()-begin));


            int[] lock = new int[0];
            begin = System.nanoTime();
            for(int i=0;i0;
                t[i]=new Thread(new Runnable(){

                    @Override
                    public void run() {
                        for(int j=0;jlock){
                                ++synValue;
                            }
                        }
                    }

                });
            }
            for(int i=0;i//main线程等待前面开启的所有线程结束
            for(int i=0;itry {
                    t[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("使用synchronized所花费的时间为:"+(System.nanoTime()-begin));

        }

    }

结果如下:

使用lock所花费的时间为:489742547
使用synchronized所花费的时间为:1660636784

从时间数字来看,可以说明,使用lock的性能要好。

在《深入理解Java虚拟机》这本书上,作者说了这句话:与其说ReentrantLock性能好,还不如说synchronized还有很大优化的余地。在JDK1.6之后,人们发现synchronized与ReentrantLock的性能基本上是完全持平的(上面测试的JDK是1.8,不知道为什么没有持平)。虚拟机在未来的性能改进中肯定会更加偏向于原生的synchronized,所以还是提倡synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

无论别人怎么说,测试才是王道。当确实synchroinzed同步时我们的性能瓶颈时,我们可以用ReentrantLock来进行性能的测试,如果确实更优,我们就可以选择用ReetrantLock来进行同步。

用Lock来进行同步计数和使用AtomicInteger类计数的性能比较

纯属好奇,刚好用Lock来模拟了下AtmoicInteger,因此,我也就比较了下这两个类在开启10个线程,每个线程计数到1000000的时间。

代码如下:

    import java.util.concurrent.atomic.AtomicInteger;

    public class TestAtomicIntegerLock2 {

        private static int synValue = 0;
        public static void main(String[] args) {
            int threadNum = 10;
            int maxValue = 1000000;

            Thread[] t = new Thread[threadNum];
            Long begin = System.nanoTime();
            for(int i=0;inew AtomicIntegerLock(0);
                t[i]=new Thread(new Runnable(){

                    @Override
                    public void run() {
                        for(int j=0;jfor(int i=0;i//main线程等待前面开启的所有线程结束
            for(int i=0;itry {
                    t[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("使用lock所花费的时间为:"+(System.nanoTime()-begin));


            int[] lock = new int[0];
            begin = System.nanoTime();
            for(int i=0;inew AtomicInteger(0);
                t[i]=new Thread(new Runnable(){

                    @Override
                    public void run() {
                        for(int j=0;jfor(int i=0;i//main线程等待前面开启的所有线程结束
            for(int i=0;itry {
                    t[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("使用原子操作类AtomicInteger所花费的时间为:"+(System.nanoTime()-begin));

        }

    }

运行结果如下:

使用lock所花费的时间为:493427269
使用原子操作类AtomicInteger所花费的时间为:85106267

可想可知,使用CAS指令确实更要快的多。

你可能感兴趣的:(java)