J.U.C学习之路

379 阅读4分钟

AQS源码解析

AQS帮我们干了啥?

  1. 内部定义了一个等待队列CLH队列,队列中每个节点持有一个线程。当当前线程抢占锁(tryAcquire)失败时,将当前线程阻塞并放到等待队列中;当锁被释放时,会唤醒等待队列的头结点,被唤醒的节点就会再次去尝试获取锁;
  2. 定义了state(int),compareAndSet(state),get(state) 获取或释放的标志位;
  3. ConditionObject 内部类是Condition接口的实现类
  4. AQS采用模板方法模式实现了获取、释放、入队/出队等主要逻辑,而真正子类只需要实现以下几个方法:
  • tryAcquire(int arg) --定义独占锁获取逻辑
  • tryRelease(int arg)-- 定义独占锁释放逻辑
  • tryAcquireShared(int arg)--定义共享锁获取逻辑
  • tryReleaseShared(int arg)--定义共享锁释放逻辑
  • isHeldExclusively()--当前线程是否持有该独占锁

如何保证线程安全

volatile+CAS(CAS是通过调用unsafe.compareAndSwapObject实现)
CAS没有成功则阻塞当前线程,并将当前线程加入到CLH等待队列

如何实现线程阻塞和唤醒

LockSupport.park(Object blocker)
LockSupport.unpark(Thread thread)
底层调用Unsafe.park,是个native方法,非java实现
可以看到一个线程A想要唤醒另一个线程B,需要先去等待队列中拿到线程B,然后调用LockSupport.unpark(Thread threadB)

公平锁/非公平锁

tryAcquire的实现不一样,公平锁在获取锁之前会判断等待队列中是否还有Predecessors,非公平锁则是直接获取锁;

  • 公平锁
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
  • 非公平锁
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

可重入锁/不可重入锁

ReentrantLock是常用的重入锁,其与不可重入锁的区别在于获取锁时先判断state,如果state>0 则继续判断获取锁的线程是否就是当前线程,如果是则获取锁并且state++,具体实现:

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

独占锁/共享锁

ReentrantLock就是独占锁,同时只能有一个线程获取到锁,ReentrantReadWriteLock.ReadLock是共享锁,可以同时有多个线程获取到读锁,但如果写锁被获取时,则读锁也会阻塞等待,读写锁只有读读可以共享。

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

锁降级

将获取到的写锁降级为读锁,降级过程:
1.获取写锁
2.获取读锁
3.释放写锁

悲观锁/乐观锁

synchronized、ReentrantLock属于悲观锁实现,就是悲观的认为竞争环境恶劣,上来直接加锁,获取不到就阻塞线程,放入到阻塞队列中,这在linux中也叫非忙等待锁;
CAS则是乐观锁的实现,认为竞争环境没那么恶劣,所以不加锁,而是通过CAS+自旋来处理竞争情况,在linux中叫自旋锁,或者忙等待锁,所以竞争不激烈的情况下乐观锁效率更高。java中的原子类都是CAS+自旋实现,可以理解为乐观锁。

AQS的应用实例

CountDownLatch

CountDownLatch又叫计数器,他通过一个共享的计数总量来控制线程锁的获取,当计数器总量大于0时,线程将被阻塞,不能够获取锁,只有当计数器总量为0时,所有被阻塞的线程同时被释放。应用实例:

public class Starter {
    private static final int NCPU = Runtime.getRuntime().availableProcessors();
    private static CountDownLatch countDownLatch = new CountDownLatch(NCPU);
    private static ThreadPoolExecutor threadPoolExecutor;

    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("game-executor-%d").build();
        initThreadPoolExecutor(threadFactory);
        startGame();
        loadData();
    }

    private static void initThreadPoolExecutor(ThreadFactory threadFactory) {
        threadPoolExecutor = new ThreadPoolExecutor(NCPU, NCPU, 50L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(6), threadFactory, new ThreadPoolExecutor.AbortPolicy());
    }

    private static void startGame() {
        threadPoolExecutor.execute(() -> {
            try {
                countDownLatch.await();
                System.out.println(Thread.currentThread().getName() + "--startGame");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    private static void loadData() {
        for (int i = 0; i < NCPU; i++) {
            threadPoolExecutor.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "--loadData");
                    countDownLatch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            });
        }
    }
}

执行结果:

game-executor-1--loadData  
game-executor-2--loadData  
game-executor-3--loadData  
game-executor-1--loadData   
game-executor-0--startGame   

countDownLatch是通过AQS的共享锁实现的,主要是await()和countDown()方法,

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
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;
            }
        }
  • tryAcquireShared方法是判断state是否到0了,如果还没有减到零,就通过doAcquireSharedInterruptibly(继承自AQS)获取共享锁;
  • countDown方法是主要调用tryReleaseShared对state进行减1操作,如果减到0了,就调用doAcquireSharedInterruptibly(继承自AQS)释放共享锁,从而唤醒所有的阻塞线程;

Semaphore

Semaphore就像旋转寿司店,共有10个座位,当座位有空余时,等待的人就可以坐上去。如果有只有2个空位,来的是一家3口,那就只有等待。如果来的是一对情侣,就可以直接坐上去吃。当然如果同时空出5个空位,那一家3口和一对情侣可以同时上去吃。CountDownLatch就像大型商场里面的临时游乐场,每一场游乐的时间过后等待的人同时进场玩,而一场中间会有不爱玩了的人随时出来,但不能进入,一旦所有进入的人都出来了,新一批人就可以同时进场。juejin.im/post/684490…

CyclicBarrier

相对于CountDownLatch是一次性对象,一旦进入终止状态,就不能被重置,CyclicBarrier可以反复使用。CyclicBarrier类似于闭锁,与闭锁的关键区别在于,闭锁用于等待事件,栅栏用于等待其他线程,其作用是让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 juejin.im/post/684490…

synchronized底层原理

www.cnblogs.com/aspirant/p/…

线程状态转换

Thread.join()底层实现是Object.wait()所以状态是waiting,会释放锁资源
Future.get()底层实现是LockSupport.lock()所以状态也是waiting
Thread.yield()线程状态变成ready,释放cpu资源,但不会释放锁资源

threadLocal

内存泄露问题

线程传递问题

线程安全问题