06.AQS和volatile

136 阅读2分钟

1.AQS 介绍

AQS中维护了一个volatile int state(共享资源)和一个CLH队列。当state=1时代表当前对象锁已经被占用,其他线程来加锁时则会失败,失败的线程被放入一个FIFO的等待队列中,然后会被 UNSAFE.park() 操作挂起,等待已经获得锁的线程释放锁才能被唤醒。 一般常用的ReentrantLock,CountDownLatch,Semaphore 都有使用到AQS;

2.手动实现独占锁和重入锁

package com.company.test.lock;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class ReenterLock implements Lock {
    private static class Sync extends AbstractQueuedSynchronizer {
      
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            } else if (getExclusiveOwnerThread() == Thread.currentThread()) {
                setState(getState() + 1);
                return true;
            }
            return false;
        }
 
        @Override
        protected boolean tryRelease(int arg) {
            if (getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setState(getState() - 1);
            if (getState() == 0) {
                setExclusiveOwnerThread(null);
            }
            return true;
        }

       
        @Override
        protected boolean isHeldExclusively() {
            return getState() > 0;
        }

        public ConditionObject newCondition() {
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();
    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

测试

package com.company.test.lock;

public class LockTest {
    public static void main(String[] args) {
        ReenterLock lock = new ReenterLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "getLock");
                printName();
                System.out.println(Thread.currentThread().getName() + "unLock");
                lock.unlock();
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);
        final Thread thread4 = new Thread(runnable);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public static void printName() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"end task");
    }
}

输出结果

Thread-2getLock
Thread-2end task
Thread-2unLock
Thread-3getLock
Thread-3end task
Thread-3unLock
Thread-0getLock
Thread-0end task
Thread-0unLock
Thread-1getLock
Thread-1end task
Thread-1unLock

可以发现,锁机制成功

3.JMM

java 的线程模型,区分别为主内存和工作内存. 主内存和工作内存是java模型里面的逻辑概念,工作内存主要是CPU 寄存器和L1,L2,L3 等高速缓存,主内存主要是设备的物理内存. 线程读取时候都是从主内存把数据读取到自己的工作内存,修改后再次写入主内存. 线程的各个工作内存相互间不能相互访问. 一般认为CPU 是计算逻辑单元,对于CPU来说,从主内存中读取数据占用的时间,要远远大于读取完毕之后计算数据的时间.

image.png

4.volatile

volatile 是JVM里面最轻量级的线程同步工具,最适合一个线程写入数据,多个线程读取数据的"一写多读"情况. volatile 能保证可见性和有序性.用volatile 标注的变量,能及时的写入主内存,并使得其他工作线程的工作内存副本失效,再次使用时候需要读取主内存的值. 使用volatile 也能保证指令重排的有序性. 在汇编语言里面有lock 内存屏障,保证了有序性和可见性.