zoukankan      html  css  js  c++  java
  • 自旋锁浅析

      自旋锁的洋名叫spin lock,是一种比较有个性的锁,因为它站在传统的互斥锁的对立面。如果并发时,互斥锁的做法是让线程阻塞,但自旋锁却不这么做,而是原地打转,不停的去抢锁,抢不到誓不罢休。简而言之,互斥锁是重量级(悲观)锁,自旋锁是轻量级(乐观)锁。自旋锁使用场景是:1、多核处理器,2、线程等待锁的时间很短,短到比线程两次上下文切换时间还少,说白了就是锁里操作的事情很简单。

      如何实现自旋锁呢?惟有CAS。何谓CAS?它的洋名叫Compare And Swap,简单来说就是比较并交换。该算法涉及三个数:内存值V,旧的预期值A,新的预期值B。当且仅当旧的预期值A和内存值V相同时,将内存值改为B,否则什么也不做。

      CAS 是实现自旋锁的基础(也是实现乐观锁的基础),CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果。先看个JDK实例:

    public class CountDownLatch {
        /**
         * Synchronization control For CountDownLatch.
         * Uses AQS state to represent count.
         */
        private static final class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = 4982264981922014374L;
    
            Sync(int count) {
                setState(count);
            }
    
            int getCount() {
                return getState();
            }
    
            protected int tryAcquireShared(int acquires) {
                return (getState() == 0) ? 1 : -1;
            }
    
            protected boolean tryReleaseShared(int releases) {
                // Decrement count; signal when transition to zero
                for (;;) {
                    int c = getState();
                    if (c == 0)
                        return false;
                    int nextc = c-1;
                    if (compareAndSetState(c, nextc))
                        return nextc == 0;
                }
            }
        }
    
        private final Sync sync;
    
        /**
         * Constructs a {@code CountDownLatch} initialized with the given count.
         *
         * @param count the number of times {@link #countDown} must be invoked
         *        before threads can pass through {@link #await}
         * @throws IllegalArgumentException if {@code count} is negative
         */
        public CountDownLatch(int count) {
            if (count < 0) throw new IllegalArgumentException("count < 0");
            this.sync = new Sync(count);
        }
    
        /**
         * Causes the current thread to wait until the latch has counted down to
         * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
         *
         * <p>If the current count is zero then this method returns immediately.
         *
         * <p>If the current count is greater than zero then the current
         * thread becomes disabled for thread scheduling purposes and lies
         * dormant until one of two things happen:
         * <ul>
         * <li>The count reaches zero due to invocations of the
         * {@link #countDown} method; or
         * <li>Some other thread {@linkplain Thread#interrupt interrupts}
         * the current thread.
         * </ul>
         *
         * <p>If the current thread:
         * <ul>
         * <li>has its interrupted status set on entry to this method; or
         * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
         * </ul>
         * then {@link InterruptedException} is thrown and the current thread's
         * interrupted status is cleared.
         *
         * @throws InterruptedException if the current thread is interrupted
         *         while waiting
         */
        public void await() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
    
        /**
         * Causes the current thread to wait until the latch has counted down to
         * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
         * or the specified waiting time elapses.
         *
         * <p>If the current count is zero then this method returns immediately
         * with the value {@code true}.
         *
         * <p>If the current count is greater than zero then the current
         * thread becomes disabled for thread scheduling purposes and lies
         * dormant until one of three things happen:
         * <ul>
         * <li>The count reaches zero due to invocations of the
         * {@link #countDown} method; or
         * <li>Some other thread {@linkplain Thread#interrupt interrupts}
         * the current thread; or
         * <li>The specified waiting time elapses.
         * </ul>
         *
         * <p>If the count reaches zero then the method returns with the
         * value {@code true}.
         *
         * <p>If the current thread:
         * <ul>
         * <li>has its interrupted status set on entry to this method; or
         * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
         * </ul>
         * then {@link InterruptedException} is thrown and the current thread's
         * interrupted status is cleared.
         *
         * <p>If the specified waiting time elapses then the value {@code false}
         * is returned.  If the time is less than or equal to zero, the method
         * will not wait at all.
         *
         * @param timeout the maximum time to wait
         * @param unit the time unit of the {@code timeout} argument
         * @return {@code true} if the count reached zero and {@code false}
         *         if the waiting time elapsed before the count reached zero
         * @throws InterruptedException if the current thread is interrupted
         *         while waiting
         */
        public boolean await(long timeout, TimeUnit unit)
            throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }
    
        /**
         * Decrements the count of the latch, releasing all waiting threads if
         * the count reaches zero.
         *
         * <p>If the current count is greater than zero then it is decremented.
         * If the new count is zero then all waiting threads are re-enabled for
         * thread scheduling purposes.
         *
         * <p>If the current count equals zero then nothing happens.
         */
        public void countDown() {
            sync.releaseShared(1);
        }
    
        /**
         * Returns the current count.
         *
         * <p>This method is typically used for debugging and testing purposes.
         *
         * @return the current count
         */
        public long getCount() {
            return sync.getCount();
        }
    
        /**
         * Returns a string identifying this latch, as well as its state.
         * The state, in brackets, includes the String {@code "Count ="}
         * followed by the current count.
         *
         * @return a string identifying this latch, as well as its state
         */
        public String toString() {
            return super.toString() + "[Count = " + sync.getCount() + "]";
        }
    }

      看上面标红那里,就是自旋锁实现的关键:1、无限循环;2、CAS。接下来再看自旋锁的实现与应用场景:

    package com.wulinfeng.test.testpilling.util;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    /**
     * 自旋锁
     *
     * @author wulinfeng
     * @version C10 2018年12月21日
     * @since SDP V300R003C10
     */
    public class SpinLock implements Lock
    {
        // 利用AtomicBoolean来调用CAS,ab初始(内存)值是false
        private AtomicBoolean ab = new AtomicBoolean(false);
        
        @Override
        public void lock()
        {
            /*
             * getAndSet将ab设置为true,并返回ab之前(内存)的值。 
             * 因为ab的初始(内存)值就是false,所以第一个线程不会进入循环,也就是说它抢到了锁
             * 而后面的线程来的时候,内存值已经是true,将进入循环自旋
             */
            while (ab.getAndSet(true))
            {
            }
            
        }
        
        @Override
        public void unlock()
        {
            // 将内存值重新设置为false
            ab.set(false);
        }
        
        @Override
        public void lockInterruptibly()
            throws InterruptedException
        {
            // TODO Auto-generated method stub
            
        }
        
        @Override
        public boolean tryLock()
        {
            // TODO Auto-generated method stub
            return false;
        }
        
        @Override
        public boolean tryLock(long time, TimeUnit unit)
            throws InterruptedException
        {
            // TODO Auto-generated method stub
            return false;
        }
        
        
        @Override
        public Condition newCondition()
        {
            // TODO Auto-generated method stub
            return null;
        }
        
    }

      测试代码:

    package com.wulinfeng.test.testpilling;
    
    import java.util.concurrent.CountDownLatch;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import com.wulinfeng.test.testpilling.util.SpinLock;
    
    import junit.framework.TestCase;
    
    public class SpinLockTest
    {
        // 开始时间
        private long startTime = 0L;
        
        // 计数器
        private int count = 0;
        
        // 让Junit支持多线程,10个线程就先初始化10
        private CountDownLatch latch = new CountDownLatch(10);
        
        @Before
        public void before()
        {
            startTime = System.currentTimeMillis();
        }
        
        @After
        public void after()
        {
            System.out.printf("count值:%d, 耗时:%d毫秒.
    ", count, System.currentTimeMillis() - startTime);
        }
        
        @Test
        public void testSpinLock()
        {
            // 初始化自旋锁
            SpinLock sl = new SpinLock();
            
            for (int i = 0; i < 10; i++)
            {
                new Thread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        for (int j = 0; j < 1000; j++)
                        {
                   // 加锁
                           sl.lock();
    // 自增 count++;

                           // 解锁
                           sl.unlock();
    }
    // 一个线程执行完了就减1,10个线程执行完了就变成0,执行主线程 latch.countDown(); } }).start(); } // 主线程等待 try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } TestCase.assertEquals(count, 10000); } }

      输出结果:

    count值:10000, 耗时:9毫秒.
  • 相关阅读:
    [BZOJ4869][洛谷P3747][六省联考2017]相逢是问候(线段树)
    [WC2014][BZOJ3435][洛谷P3920]紫荆花之恋(动态点分治+treap)
    JavaScript对象JQuery In Action
    每日一条SQL LEFT JOIN
    Div border 显示不出来的原因
    HTML列表标记:dl、dt、dd
    The Effective Executive 笔记 一
    c# 解析JSON的几种办法
    使用if else 容易犯的错
    每日一句SQL:内联视图
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/10152178.html
Copyright © 2011-2022 走看看