美文网首页javacode
动手实现一个同步器(AQS)

动手实现一个同步器(AQS)

作者: 九点半的马拉 | 来源:发表于2020-02-26 23:28 被阅读0次

在多线程情景下,如果不会某一共享变量采取一些同步机制,很可能发生数据不安全现象,比如购买车票时,当多个人购买时,不加锁就会产生多人买同一张票的现象,显然这是不可取的。所以要有一种同步机制,在某一时刻只能有一个线程处理该共享变量。

同步器的加锁

我将自己实现的同步器成为RoadAQS.
主要变量如下:

//当前锁的状态,1表示加锁,0表示未加锁
private volatile int state = 0;
private final static Unsafe unsafe = UnsafeInstance.reflectUnsafe();
//state在内存中的偏移量
private final static long stateOffset;
//当前持有锁的线程
private Thread lockHoder;
//是一个线程安全的队列,记录等待获取锁的线程
private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();

static {
        try {
            stateOffset = unsafe.objectFieldOffset(RoadAQS.class.getDeclaredField("state"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

整体思想:
刚一开始初始化时会利用反射获取一个Unsafe魔法类,然后获取变量state在内存中的偏移量,为后续的CAS操作做准备。然后开始尝试获取锁,当等待队列为空或者当前线程等于等待队列的第一个线程,然后CAS更新状态为1成功,说明获得锁成功,并将同步器的拥有者设置为当前线程。如果加锁失败,就将该线程放入到等待队列中,然后开始无限for循环。
进入循环背内部,再尝试一次获取锁,仍然失败后,开始调用LockSupport.park()将该线程进行阻塞,与Object.wait一个最大的区别就是park()、unpark()能够指定具体的线程进行唤醒,而object.notify只能随机唤醒一个。
阻塞后当其他线程执行完退出后,会调用LockSupport.unpark(t)对等待队列中的第一个线程进行唤醒,唤醒后会继续执行for循环内部的代码,再尝试获得锁。获得锁后,从等待队列中取出,并将同步器的拥有者改为该线程。

public void lock() {
        if(acquire()){
            return;
        }
        Thread current = Thread.currentThread();
        waiters.add(current);
        for(;;) {
            if(current == waiters.peek() && acquire()) {
                waiters.poll();
                return;
            }
            LockSupport.park();
        }
    }
public boolean acquire() {
        Thread t = Thread.currentThread();
        if ((waiters.size() == 0 || t == waiters.peek()) && compareAndSwapInt(0, 1)) {
            setLockHoder(t);
            return true;
        }
        return false;
    }

同步器的解锁

获取当前的锁状态,并尝试更新为0,成功后将同步器的拥有者设为null,然后获取等待队列的第一个队列,将该队列进行唤醒。

public void unlock() {
        if (Thread.currentThread() != getLockHolder()) {
            throw new RuntimeException("lockHolder is not current Thread");
        }
        int state = getState();
        if (compareAndSwapInt(state, 0)) {
            setLockHoder(null);
            Thread t = waiters.peek();
            if (t != null) {
                LockSupport.unpark(t);
            }
        }
    }

测试用例

public class RoadAQSTest {
    public static void main(String[] args) {
        Goods goods = new Goods();
        for(int i=0; i<100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    goods.reduceCount();
                }
            }, "Thread-" + i + "------").start();
        }
    }

    private static class Goods{
        private int count = 10;
        private RoadAQS lock = new RoadAQS();
        public void reduceCount() {

            lock.lock();

            if (count > 0) {
                System.out.println("线程" + lock.getLockHolder() + " 获取第 " + count + "件商品");
                count--;
            } else {
                System.out.println("商品已卖完!");
            }
            lock.unlock();
        }
    }
}

测试结果:

测试结果.png

相关文章

  • 第七章

    AbstractQueuedSynchronizer——AQS 抽象排队同步器 AQS实现: 1.使用Node实现...

  • 笔记:多线程并发编程(3)AQS、 syncionzerd 和v

    AQS(队列同步器AbstractQueuedSynchronizer): AQS使用了模板方法 设计模式 实现A...

  • Java - AQS原理

    AQS 实现原理 AQS:AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同...

  • JAVA-AQS

    什么是AQS? 队列同步器AbstractQueuedSynchronizer(以下简称同步器或AQS),就是一个...

  • 动手实现一个同步器(AQS)

    在多线程情景下,如果不会某一共享变量采取一些同步机制,很可能发生数据不安全现象,比如购买车票时,当多个人购买时,不...

  • AQS

    AQS 的全称是 Abstract Queued Synchronizer,也就是基于队列实现的抽象同步器 AQS...

  • 2019-04-16——Java 并发包 锁 AQS

    AQS AbstractQueuedSynchronizer简称AQS。是一个用于构建锁和同步器的框架,许多同步器...

  • AbstractQueuedSynchronizer 队列同步器

    AbstractQueuedSynchronizer 队列同步器(AQS) 队列同步器 (AQS), 是用来构建锁...

  • AQS

    AQS官方解读 AQS 只是一个框架,具体资源的获取/释放方式交由自定义同步器去实现,AQS 这里只定义了一个接口...

  • 并发编程(六)ReentrantlLock实现原理

    AQS AQS全称是Abstract Queued Synchronizer,翻译为同步器,它是一套实现多线程同步...

网友评论

    本文标题:动手实现一个同步器(AQS)

    本文链接:https://www.haomeiwen.com/subject/xkqnchtx.html