java锁实现_Java锁实现

java锁实现

我们都将第三方库用作开发的正常部分。 通常,我们无法控制其内部。 JDK随附的库是一个典型示例。 这些库中的许多库都使用锁来管理争用。
JDK锁具有两种实现。 一个使用原子CAS样式指令来管理索赔过程。 CAS指令往往是最昂贵的CPU指令类型,并且在x86上具有内存排序语义。 锁通常是无竞争的,这会导致可能的优化,从而可以使用避免使用原子指令的技术将锁偏向无竞争的线程。 这种偏向使得理论上的锁定可以被同一线程快速重新获得。 如果该锁最终被多个线程争用,则该算法将从被偏向的状态恢复,并使用原子指令退回到标准方法。 偏向锁定已成为Java 6的默认锁定实现
在遵守单一作者原则时,偏向锁定应该是您的朋友。 最近,当使用套接字API时,我决定测量锁定成本,并对结果感到惊讶。 我发现我的无竞争线程所产生的开销比锁期望的要多。 我汇总了以下测试,以比较Java 6中当前可用的锁实现的成本。
考试
为了进行测试,我将在锁中增加一个计数器,并增加锁中竞争线程的数量。 对于Java可用的3种主要锁实现,将重复此测试:
  1. Java语言监视器上的原子锁定
  2. Java语言监视器上的偏向锁定
  3. Java 5中随java.util.concurrent包引入的ReentrantLock
我还将在最新的3代Intel CPU上运行测试。 对于每个CPU,我将执行测试,直到核心计数将支持的最大并发线程数。
该测试是在64位Linux(Fedora Core 15)和Oracle JDK 1.6.0_29上进行的。

代码

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

import static java.lang.System.out;

public final class TestLocks implements Runnable
{
    public enum LockType { JVM, JUC }
    public static LockType lockType;

    public static final long ITERATIONS = 500L * 1000L *1000L;
    public static long counter = 0L;

    public static final Object jvmLock = new Object();
    public static final Lock jucLock = new ReentrantLock();
    private static int numThreads;
    private static CyclicBarrier barrier;

    public static void main(final String[] args) throws Exception
    {
        lockType = LockType.valueOf(args[0]);
        numThreads = Integer.parseInt(args[1]);
        
        runTest(numThreads); // warm up
        counter = 0L;

        final long start = System.nanoTime();
        runTest(numThreads);
        final long duration = System.nanoTime() - start;

        out.printf("%d threads, duration %,d (ns)\n", numThreads, duration);
        out.printf("%,d ns/op\n", duration / ITERATIONS);
        out.printf("%,d ops/s\n", (ITERATIONS * 1000000000L) / duration);
        out.println("counter = " + counter);
    }

    private static void runTest(final int numThreads) throws Exception
    {
        barrier = new CyclicBarrier(numThreads);
        Thread[] threads = new Thread[numThreads];

        for (int i = 0; i < threads.length; i++)
        {
            threads[i] = new Thread(new TestLocks());
        }

        for (Thread t : threads)
        {
            t.start();
        }

        for (Thread t : threads)
        {
            t.join();
        }
    }

    public void run()
    {
        try
        {
            barrier.await();
        }
        catch (Exception e)
        {
            // don't care
        }

        switch (lockType)
        {
            case JVM: jvmLockInc(); break;
            case JUC: jucLockInc(); break;
        }
    }

    private void jvmLockInc()
    {
        long count = ITERATIONS / numThreads;
        while (0 != count--)
        {
            synchronized (jvmLock)
            {
                ++counter;
            }
        }
    }

    private void jucLockInc()
    {
        long count = ITERATIONS / numThreads;
        while (0 != count--)
        {
            jucLock.lock();
            try
            {
                ++counter;
            }
            finally
            {
                jucLock.unlock();
            }
        }
    }
}

编写测试脚本:

设置-x
对于{1..8}中的i; 做Java -XX:-UseBiasedLocking TestLocks JVM $ i; 做完了
对于{1..8}中的i; 做Java -XX:+ UseBiasedLocking TestLocks JVM $ i; 做完了 对于{1..8}中的i; 做Java TestLocks JUC $ i; 做完了

结果

图1
图2
图3
在现代英特尔处理器上,偏置锁定不再应该是默认的锁定实现。 我建议您使用-XX:-UseBiasedLocking JVM选项来衡量应用程序和实验的性能,以确定是否可以从无竞争情况下使用基于原子锁的算法中受益。

观察结果
  1. 在无竞争的情况下,有偏锁比原子锁贵10%。 似乎对于最近的CPU代来说,原子指令的成本比偏向锁的必要内务处理要少。 在Nehalem之前,锁定指令会在内存总线上声明一个锁定以执行这些原子操作,每个操作都将花费100个以上的周期。 从Nehalem开始,原子指令可以在CPU内核本地进行处理,并且在执行内存排序语义时不需要等待存储缓冲区为空时,通常只需花费10-20个周期。
  2. 随着争用的增加,无论线程数如何,语言监视器锁定都会Swift达到吞吐量限制。
  3. 与使用同步的语言监视器相比,ReentrantLock提供了最佳的无竞争性能,并且随着争用的增加,扩展性也显着提高。
  4. 当2个线程竞争时,ReentrantLock具有降低性能的奇怪特征。 这值得进一步调查。
  5. 当竞争线程数较少时,Sandybridge遭受原子指令增加的延迟 ,这在上一篇文章中已详细介绍。 随着竞争线程数的不断增加,内核仲裁的成本趋于占主导地位,而Sandybridge在提高内存吞吐量方面显示出其优势。
结论
在开发自己的并发库时,如果无锁替代算法不是可行的选择,则建议使用ReentrantLock而不是使用synced关键字,因为它在x86上具有明显更好的性能。
更新2011年11月20日
Dave Dice指出,未对JVM启动的前几秒中创建的锁实施偏向锁。 我将在本周重新运行测试并发布结果。 我收到了更多质量反馈,表明我的结果可能无效。 微型基准测试可能会很棘手,但是在大型环境中衡量自己的应用程序的建议仍然存在。
考虑到Dave的反馈,可以在后续博客中查看测试的重新运行。
参考:来自我们的JCG合作伙伴 Martin Thompson的Java锁实现,来自Mechanical Sympathy博客。

翻译自: https://www.javacodegeeks.com/2012/07/java-lock-implementations.html

java锁实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值