轻松搞懂Java中的自旋锁

前言

在以前的文章《一文完全搞懂面试中常问的各类“锁”》中介绍了Java中的各类“锁”,可能对于不是很了解这些概念的同窗来讲会以为有点绕,因此我决定拆分出来,逐步详细的介绍一下这些锁的前因后果,那么这篇文章就先来会一会“自旋锁”。java

正文

出现缘由

在咱们的程序中,若是存在着大量的互斥同步代码,当出现高并发的时候,系统内核态就须要不断的去挂起线程和恢复线程,频繁的此类操做会对咱们系统的并发性能有必定影响。同时聪明的JVM开发团队也发现,在程序的执行过程当中锁定“共享资源“的时间片是极短的,若是仅仅是为了这点时间而去不断挂起、恢复线程的话,消耗的时间可能会更长,那就“捡了芝麻丢了西瓜”了。git

而在一个多核的机器中,多个线程是能够并行执行的。若是当后面请求锁的线程没拿到锁的时候,不挂起线程,而是继续占用处理器的执行时间,让当前线程执行一个忙循环(自旋操做),也就是不断在盯着持有锁的线程是否已经释放锁,那么这就是传说中的自旋锁了。github

自旋锁开启

虽然在JDK1.4.2的时候就引入了自旋锁,可是须要使用“-XX:+UseSpinning”参数来开启。在到了JDK1.6之后,就已是默认开启了。下面咱们本身来实现一个基于CAS的简易版自旋锁。web

public class SimpleSpinningLock {

    /** * 持有锁的线程,null表示锁未被线程持有 */
    private AtomicReference<Thread> ref = new AtomicReference<>();

    public void lock(){
        Thread currentThread = Thread.currentThread();
        while(!ref.compareAndSet(null, currentThread)){
            //当ref为null的时候compareAndSet返回true,反之为false
            //经过循环不断的自旋判断锁是否被其余线程持有
        }
    }

    public void unLock() {
        Thread cur = Thread.currentThread();
        if(ref.get() != cur){
            //exception ...
        }
        ref.set(null);
    }
}

简简单单几行代码就实现了一个简陋的自旋锁,下面咱们来测试一下面试

public class TestLock {

    static int count  = 0;

    public static void main(String[] args) throws InterruptedException {
       ExecutorService executorService = Executors.newFixedThreadPool(100);
       CountDownLatch countDownLatch = new CountDownLatch(100);
       SimpleSpinningLock simpleSpinningLock = new SimpleSpinningLock();
       for (int i = 0 ; i < 100 ; i++){
           executorService.execute(new Runnable() {
               @Override
               public void run() {
                   simpleSpinningLock.lock();
                   ++count;
                   simpleSpinningLock.unLock();
                   countDownLatch.countDown();
               }
           });

       }
       countDownLatch.await();
       System.out.println(count);
    }
}

// 屡次执行输出均为:100 ,实现了锁的基本功能

经过上面的代码能够看出,自旋就是在循环判断条件是否知足,那么会有什么问题吗?若是锁被占用很长时间的话,自旋的线程等待的时间也会变长,白白浪费掉处理器资源。所以在JDK中,自旋操做默认10次,咱们能够经过参数“-XX:PreBlockSpin”来设置,当超过来此参数的值,则会使用传统的线程挂起方式来等待锁释放。并发

自适应自旋锁

随着JDK的更新,在1.6的时候,又出现了一个叫作“自适应自旋锁”的玩意。它的出现使得自旋操做变得聪明起来,再也不跟以前同样死板。所谓的“自适应”意味着对于同一个锁对象,线程的自旋时间是根据上一个持有该锁的线程的自旋时间以及状态来肯定的。例如对于A锁对象来讲,若是一个线程刚刚经过自旋得到到了锁,而且该线程也在运行中,那么JVM会认为这次自旋操做也是有很大的机会能够拿到锁,所以它会让自旋的时间相对延长。可是若是对于B锁对象自旋操做不多成功的话,JVM甚至可能直接忽略自旋操做。所以,自适应自旋锁是一个更加智能,对咱们的业务性能更加友好的一个锁。ide

结语

原本想着在一篇文章里面把“自旋锁”,“锁消除”,“锁粗化”等一些锁优化的概念都介绍完成的,可是发现可能篇幅会比较大,对于没怎么接触过这一块的同窗来讲理解起来会比较吃力,因此决定分开多个章节介绍,但愿你们都不懂的地方能够多看几遍,慢慢体会,相信你会有所收获的。svg


公众号博文同步Github仓库,有兴趣的朋友能够帮忙给个Star哦,码字不易,感谢支持。高并发

https://github.com/PeppaLittlePig/blog-wechat性能

推荐阅读

如何优化代码中大量的if/else,switch/case?
如何提升使用Java反射的效率?
Java日志正确使用姿式

有收获的话,就点个赞吧

关注「深夜里的程序猿」,分享最干的干货