在多线程情景下,如果不会某一共享变量采取一些同步机制,很可能发生数据不安全现象,比如购买车票时,当多个人购买时,不加锁就会产生多人买同一张票的现象,显然这是不可取的。所以要有一种同步机制,在某一时刻只能有一个线程处理该共享变量。
同步器的加锁
我将自己实现的同步器成为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(); } } }
测试结果:
来源:https://www.cnblogs.com/maratong/p/12369907.html