标签:alt sys 一点 wapi var 个数 改进 cas abstract
Lock接口
void lock() // 如果锁可用就获得锁,如果锁不可用就阻塞直到锁释放 void lockInterruptibly() // 和lock()方法相似, 但阻塞的线程 可 中 断 , 抛 出java.lang.InterruptedException 异常 boolean tryLock() // 非阻塞获取锁;尝试获取锁,如果成功返回 true boolean tryLock(long timeout, TimeUnit timeUnit) //带有超时时间的获取锁方法 void unlock() // 释放锁
public class ReentrantDemo{ public synchronized void demo(){ System.out.println("begin:demo"); demo2(); } public void demo2(){ System.out.println("begin:demo1"); synchronized (this){ } } public static void main(String[] args) { ReentrantDemo rd=new ReentrantDemo(); new Thread(rd::demo).start(); } }
ReentrantLock 的使用案例
public class AtomicDemo { private static int count=0; static Lock lock=new ReentrantLock(); public static void inc(){ lock.lock(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } count++; lock.unlock(); } public static void main(String[] args) throws InterruptedException { for(int i=0;i<1000;i++){ new Thread(()->{AtomicDemo.inc();}).start();; } Thread.sleep(3000); System.out.println("result:"+count); } }
package com.lf.threaddemo; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class LockDemo { static Map<String, Object> cacheMap = new HashMap<>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock read = rwl.readLock(); static Lock write = rwl.writeLock(); public static final Object get(String key) { System.out.println("开始读取数据"); read.lock(); //读锁 try { return cacheMap.get(key); } finally { read.unlock(); } } public static final Object put(String key, Object value) { write.lock(); System.out.println("开始写数据"); try { return cacheMap.put(key, value); } finally { write.unlock(); } } }
StampedLock 支持三种模式,分别是:写锁、悲观读锁和乐观读。其中,写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似,
允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的。不同的是:StampedLock 里的写锁和悲观读锁加锁成功之后,
都会返回一个 stamp;然后解锁的时候,需要传入这个 stamp。相关的示例代码如下。
final StampedLock sl = new StampedLock(); // 获取/释放悲观读锁示意代码 long stamp = sl.readLock(); try { //省略业务相关代码 } finally { sl.unlockRead(stamp); } // 获取/释放写锁示意代码 long stamp = sl.writeLock(); try { //省略业务相关代码 } finally { sl.unlockWrite(stamp); }
StampedLock 的性能之所以比 ReadWriteLock 还要好,其关键是 StampedLock 支持乐观读的方式。
ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,
所有的写操作会被阻塞;而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。
注意这里,我们用的是“乐观读”这个词,而不是“乐观读锁”,是要提醒你,乐观读这个操作是无锁的,所以相比较 ReadWriteLock 的读锁,乐观读的性能更好一些。
StampedLock 使用注意事项对于读多写少的场景 StampedLock 性能很好,简单的应用场景基本上可以替代 ReadWriteLock,
但是 StampedLock 的功能仅仅是 ReadWriteLock 的子集,在使用的时候,还是有几个地方需要注意一下。
StampedLock 在命名上并没有增加 Reentrant,想必你已经猜测到 StampedLock 应该是不可重入的。
事实上,的确是这样的,StampedLock 不支持重入。这个是在使用中必须要特别注意的。
另外,StampedLock 的悲观读锁、写锁都不支持条件变量,这个也需要你注意。还有一点需要特别注意,
那就是:如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。
例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;
如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。
final StampedLock lock = new StampedLock(); Thread T1 = new Thread(()->{ // 获取写锁 lock.writeLock(); // 永远阻塞在此处,不释放写锁 LockSupport.park(); }); T1.start(); // 保证T1获取写锁 Thread.sleep(100); Thread T2 = new Thread(()-> //阻塞在悲观读锁 lock.readLock() ); T2.start(); // 保证T2阻塞在读锁 Thread.sleep(100); //中断线程T2 //会导致线程T2所在CPU飙升 T2.interrupt(); T2.join();
所以,使用 StampedLock 一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。这个规则一定要记清楚
StampedLock 的使用看上去有点复杂,但是如果你能理解乐观锁背后的原理,使用起来还是比较流畅的。建议你认真揣摩 Java 的官方示例,这个示例基本上就是一个最佳实践。
我们把 Java 官方示例精简后,形成下面的代码模板,建议你在实际工作中尽量按照这个模板来使用 StampedLock。
StampedLock 读模板:
final StampedLock sl = new StampedLock(); // 乐观读 long stamp = sl.tryOptimisticRead(); // 读入方法局部变量 ...... // 校验stamp if (!sl.validate(stamp)){ // 升级为悲观读锁 stamp = sl.readLock(); try { // 读入方法局部变量 ..... } finally { //释放悲观读锁 sl.unlockRead(stamp); } } //使用方法局部变量执行业务操作 ......
StampedLock 写模板:
long stamp = sl.writeLock(); try { // 写共享变量 ...... } finally { sl.unlockWrite(stamp); }
public void lock() { sync.lock(); }
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
CAS 的实现原理
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
在 unsafe.cpp 文件中,可以找到 compareAndSwarpInt 的实现 UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj); //将 Java 对象解析成 JVM 的 oop(普通对象指针), jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); //根据对象 p和地址偏移量找到地址 return (jint)(Atomic::cmpxchg(x, addr, e)) == e; //基于 cas 比较并替换, x 表示需要更新的值,addr 表示 state 在内存中的地址,e 表示预期值 UNSAFE_END
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//获取当前执行的线程 int c = getState();//获得 state 的值 if (c == 0) {//表示无锁状态 if (compareAndSetState(0, acquires)) {//cas 替换 state 的值,cas 成功表示获取锁成功 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;
}
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode);//把当前线程封装为 Node Node pred = tail; //tail 是 AQS 中表示同比队列队尾的属性,默认是 null if (pred != null) {//tail 不为空的情况下,说明队列中存在节点 node.prev = pred;//把当前线程的 Node 的 prev 指向 tail if (compareAndSetTail(pred, node)) {//通过 cas 把 node加入到 AQS 队列,也就是设置为 tail pred.next = node;//设置成功以后,把原 tail 节点的 next指向当前 node return node; } } enq(node);//tail=null,把 node 添加到同步队列 return node; }
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } }
}
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//获取当前节点的 prev 节点 if (p == head && tryAcquire(arg)) {//如果是 head 节点,说明有资格去争抢锁 setHead(node);//获取锁成功,也就是ThreadA 已经释放了锁,然后设置 head 为 ThreadB 获得执行权限 p.next = null; //把原 head 节点从链表中移除 failed = false; return interrupted; }//ThreadA 可能还没释放锁,使得 ThreadB 在执行 tryAcquire 时会返回 false if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) interrupted = true; //并且返回当前线程在等待过程中有没有中断过。 } } finally { if (failed) cancelAcquire(node); }
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//前置节点的waitStatus if (ws == Node.SIGNAL)//如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放, return true;//返回 true,意味着可以直接放心的挂起了 if (ws > 0) {//ws 大于 0,意味着 prev 节点取消了排队,直接移除这个节点就行 do { node.prev = pred = pred.prev; //相当于: pred=pred.prev; node.prev=pred; } while (pred.waitStatus > 0); //这里采用循环,从双向列表中移除 CANCELLED 的节点 pred.next = node; } else {//利用 cas 设置 prev 节点的状态为 SIGNAL(-1) compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false;
}
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
static void selfInterrupt() { Thread.currentThread().interrupt(); }
标签:alt sys 一点 wapi var 个数 改进 cas abstract
原文地址:https://www.cnblogs.com/flgb/p/12951929.html