做网站能不能放暴露图片,龙港做网站店铺,游戏开发软件排行榜前十名,企业所得税怎样计算1、CAS
1.1 基本概念
CAS 是 compare and swap 的简写#xff0c;即比较并交换。它是指一种操作机制#xff0c;而不是某个具体的类或方法。在 Java 平台上对这种操作进行了包装。在 Unsafe 类中#xff0c;调用代码如下 这里无法用Unsafe类看#xff0c;我使用的是Atomi…1、CAS
1.1 基本概念
CAS 是 compare and swap 的简写即比较并交换。它是指一种操作机制而不是某个具体的类或方法。在 Java 平台上对这种操作进行了包装。在 Unsafe 类中调用代码如下 这里无法用Unsafe类看我使用的是AtomicInteger查看的源码 这里是三个分别是内存位置 V旧的预期值 A 和新的值 B。操作时先从内存位置读取到值然后和预期值A比较。如果相等则将此内存位置的值改为新值 B返回 true。如果不相等说明和其他线程冲突了则不做任何改变返回 false。
这种机制在不阻塞其他线程的情况下避免了并发冲突比独占锁的性能高很多。 CAS 在 Java 的原子类和并发包中有大量使用。
1.2底层实现 CAS 主要分三步读取-比较-修改。其中比较是在检测是否有冲突如果检测到没有冲突后其他线程还能修改这个值那么 CAS 还是无法保证正确性。所以最关键的是要保证比较-修改这两步操作的原子性。
CAS 底层是靠调用 CPU 指令集的 cmpxchg 完成的它是 x86 和 Intel 架构中的 compare and exchange 指令。在多核的情况下这个指令也不能保证原子性需要在前面加上 lock 指令。lock 指令可以保证一个 CPU 核心在操作期间独占一片内存区域。那么 这又是如何实现的呢
在处理器中一般有两种方式来实现上述效果
总线锁和缓存锁。
在多核处理器的结构中CPU 核心并不能直接访问内存而是统一通过一条总线访问。总线锁就是锁住这条总线使其他核心无法访问内存。这种方式代价太大了会导致其他核心停止工作。
而缓存锁并不锁定总线只是锁定某部分内存区域。当一个 CPU 核心将内存区域的数据读取到自己的缓存区后它会锁定缓存对应的内存区域。锁住期间其他核心无法操作这块内存区域。
CAS 就是通过这种方式实现比较和交换操作的原子性的。值得注意的是 CAS 只是保证了操作的原子性并不保证变量的可见性因此变量需要加上 volatile 关键字。
1.3 ABA问题
上面提到CAS 保证了比较和交换的原子性。但是从读取到开始比较这段期间其他核心仍然是可以修改这个值的。如果核心将 A 修改为 BCAS 可以判断出来。但是如果核心将 A 修改为 B 再修改回 A。那么 CAS 会认为这个值并没有被改变从而继续操作。这是和实际情况不符的。解决方案是加一个版本号。
2.AQS
2.1基本概念 全称 AbstractQueuedSynchronizer。AQS 中有两个重要的成员
成员变量 state。用于表示锁现在的状态用 volatile 修饰保证内存一致性。同时所用对 state 的操作都是使用 CAS 进行的。state 为0表示没有任何线程持有这个锁线程持有该锁后将 state 加1释放时减1。多次持有释放则多次加减。还有一个双向链表链表除了头结点外每一个节点都记录了线程的信息代表一个等待线程。这是一个 FIFO 的链表
3. ReentrantLock 3.1 基本概念
Lock和 java.io.Serializable接口并提供了与synchronized相同的互斥性和内存可见性
public class ReentrantLock implements Lock, java.io.Serializable {
.....
}
他是非公平默认也可以是非公平的是支持重入的锁
3.2 底层实现 ReentrantLock与AQS的关系非常密切 abstract static class Sync extends AbstractQueuedSynchronizer {
。。。。
} ReentrantLock的使用则是这样子
Lock lock new ReentrantLock();lock.lock();try{//更新对象状态//捕获异常并在必须时恢复不变性条件}catch (Exception e){e.printStackTrace();} finally {lock.unlock();} 因为他继承了Lock的接口i,我们也要讲下Lock的方法
方法名称方法描述lock用来获取锁如果锁已被其他线程获取则进行等待tryLock表示用来尝试获取锁如果获取成功则返回true如果获取失败即锁已被其他线程获取则返回false也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待tryLock(long time, TimeUnit unit)和tryLock()类似区别在于它在拿不到锁时会等待一定的时间在时间期限之内如果还拿不到锁就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁则返回truelockInterruptibly获取锁如果获取锁失败则进行等到如果等待的线程被中断会相应中断信息unlock释放锁的操作newCondition获取Condition对象该组件和当前的锁绑定当前线程只有获得了锁才能调用该组件wait()方法而调用后当前线程释放锁 ReentrantLock 也实现了上面接口的内容,同时 ReentrantLock 提供了 公平锁和 非公平锁两种模式如果没有特别的去指定使用何种方式那么 ReentrantLock 会默认为 非公平锁首先我们来看一下 ReentrantLock 的构造函数 /*** 无参的构造函数*/public ReentrantLock() {sync new NonfairSync();}/*** 有参构造函数* 参数为布尔类型*/public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();}
我们会发现在默认无参构造的是非公平锁即对应了我上文所说的默认是非公平的锁。
我们这里就要说下非公平锁是怎么个东西了。
3.2.1 非公平锁NonfairSync.lock() 3.2.1.1 lock方法详解
当我们调用 ReentrantLock 的 lock() 方法的时候实际上是调用了 NonfairSync 的 lock() 方法代码如下
static final class NonfairSync extends Sync {private static final long serialVersionUID 7316153563782823691L;/*** Performs lock. Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {//这个方法先用CAS操作去尝试抢占该锁// 快速尝试将state从0设置成1如果state0代表当前没有任何一个线程获得了锁if (compareAndSetState(0, 1))//state设置成1代表获得锁成功//如果成功就把当前线程设置在这个锁上表示抢占成功,在重入锁的时候需要setExclusiveOwnerThread(Thread.currentThread());else//如果失败则调用 AbstractQueuedSynchronizer.acquire() 模板方法等待抢占。acquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}调用 acquire(1) 实际上使用的是 AbstractQueuedSynchronizer 的 acquire() 方法它是一套锁抢占的模板,acquire() 代码比较简单
public final void acquire(int arg) {//先去尝试获取锁,如果没有获取成功就在CLH队列中增加一个当前线程的节点表示等待抢占。//然后进入CLH队列的抢占模式进入的时候也会去执行一次获取锁的操作如果还是获取不到//就调用LockSupport.park() 将当前线程挂起。那么当前线程什么时候会被唤醒呢当//持有锁的那个线程调用 unlock() 的时候会将CLH队列的头节点的下一个节点上的线程//唤醒调用的是 LockSupport.unpark() 方法。if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
acquire() 会先调用 tryAcquire() 这个钩子方法去尝试获取锁这个方法就是在 NonfairSync.tryAcquire()下的 **nonfairTryAcquire()**源码如下
//一个尝试插队的过程final boolean nonfairTryAcquire(int acquires) {final Thread current Thread.currentThread();//获取state值int c getState();//比较锁的状态是否为 0如果是0当前没有任何一个线程获取锁if (c 0) {//则尝试去原子抢占这个锁设置状态为1然后把当前线程设置成独占线程if (compareAndSetState(0, acquires)) {// 设置成功标识独占锁setExclusiveOwnerThread(current);return true;}}//如果当前锁的状态不是0 state!0,就去比较当前线程和占用锁的线程是不是一个线程else if (current getExclusiveOwnerThread()) {//如果是增加状态变量的值从这里看出可重入锁之所以可重入就是同一个线程可以反复使用它占用的锁int nextc c acquires;//重入次数太多大过Integer.MAXif (nextc 0) // overflowthrow new Error(Maximum lock count exceeded);setState(nextc);return true;}//如果以上两种情况都不通过则返回失败falsereturn false;}
tryAcquire() 一旦返回 false就会则进入 acquireQueued() 流程也就是基于CLH队列的抢占模式在CLH锁队列尾部增加一个等待节点这个节点保存了当前线程通过调用 addWaiter() 实现这里需要考虑初始化的情况在第一个等待节点进入的时候需要初始化一个头节点然后把当前节点加入到尾部后续则直接在尾部加入节点。
//AbstractQueuedSynchronizer.addWaiter()private Node addWaiter(Node mode) {// 初始化一个节点用于保存当前线程Node node new Node(Thread.currentThread(), mode);// 当CLH队列不为空的视乎直接在队列尾部插入一个节点Node pred tail;if (pred ! null) {node.prev pred;//如果pred还是尾部(即没有被其他线程更新)则将尾部更新为node节点(即当前线程快速设置成了队尾)if (compareAndSetTail(pred, node)) {pred.next node;return node;}}// 当CLH队列为空的时候调用enq方法初始化队列enq(node);return node;}private Node enq(final Node node) {//在一个循环里不停的尝试将node节点插入到队尾里for (;;) {Node t tail;if (t null) { // 初始化节点头尾都指向一个空节点if (compareAndSetHead(new Node()))tail head;} else {node.prev t;if (compareAndSetTail(t, node)) {t.next node;return t;}}}} 将节点增加到CLH队列后进入 acquireQueued() 方法
final boolean acquireQueued(final Node node, int arg) {boolean failed true;try {boolean interrupted false;//在一个循环里不断等待前驱节点执行完毕for (;;) {final Node p node.predecessor();if (p head tryAcquire(arg)) {// 通过tryAcquire获得锁如果获取到锁说明头节点已经释放了锁setHead(node);//将当前节点设置成头节点p.next null; // help GC//将上一个节点的next变量被设置为null在下次GC的时候会清理掉failed false;//将failed标记设置成falsereturn interrupted;}//中断if (shouldParkAfterFailedAcquire(p, node) // 是否需要阻塞parkAndCheckInterrupt())// 阻塞返回线程是否被中断interrupted true;}} finally {if (failed)cancelAcquire(node);}} 如果尝试获取锁失败就会进入 shouldParkAfterFailedAcquire() 方法会判断当前线程是否阻塞
/*** 确保当前结点的前驱结点的状态为SIGNAL* SIGNAL意味着线程释放锁后会唤醒后面阻塞的线程* 只有确保能够被唤醒当前线程才能放心的阻塞。*/private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws pred.waitStatus;if (ws Node.SIGNAL)//如果前驱节点状态为SIGNAL//表明当前线程需要阻塞因为前置节点承诺执行完之后会通知唤醒当前节点return true;if (ws 0) {//ws 0 代表前驱节点取消了do {node.prev pred pred.prev;//不断的把前驱取消了的节点移除队列} while (pred.waitStatus 0);pred.next node;} else {//初始化状态将前驱节点的状态设置成SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}
进入阻塞阶段会进入 parkAndCheckInterrupt() 方法则会调用 LockSupport.park(this) 将当前线程挂起。代码如下
// 从方法名可以看出这个方法做了两件事
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);//挂起当前的线程// 如果当前线程已经被中断了返回true否则返回false// 有可能在挂起阶段被中断了return Thread.interrupted();
} 3.2.1.2 NonfairSync.unlock()
调用 unlock() 方法其实是直接调用 AbstractQueuedSynchronizer.release() 操作。进入 release() 方法内部先尝试 tryRelease() 操作,主要是去除锁的独占线程然后将状态减一这里减一主要是考虑到可重入锁可能自身会多次占用锁只有当状态变成0才表示完全释放了锁。如果 tryRelease 成功则将CHL队列的头节点的状态设置为0然后唤醒下一个非取消的节点线程。一旦下一个节点的线程被唤醒被唤醒的线程就会进入 acquireQueued() 代码流程中去获取锁。 public void unlock() {sync.release(1);
}public final boolean release(int arg) {//尝试在当前锁的锁定计数state值上减1if (tryRelease(arg)) {Node h head;if (h ! null h.waitStatus ! 0)//waitStatus!0表明或者处于CANCEL状态,或者是SIGNAL表示下一个线程在等待其唤醒。也就是说waitStatus不为零表示它的后继在等待唤醒。unparkSuccessor(h);//成功返回truereturn true;}//否则返回falsereturn false;
}private void unparkSuccessor(Node node) {int ws node.waitStatus;//如果waitStatus 0 则将当前节点清零if (ws 0)compareAndSetWaitStatus(node, ws, 0);//若后续节点为空或已被cancel则从尾部开始找到队列中第一个waitStatus0即未被cancel的节点Node s node.next;if (s null || s.waitStatus 0) {s null;for (Node t tail; t ! null t ! node; t t.prev)if (t.waitStatus 0)s t;}if (s ! null)LockSupport.unpark(s.thread);}当然在 release() 方法中不仅仅只是将 state - 1 这么简单**-1** 之后还需要进行一番处理如果 -1 之后的 新state 0 则表示当前锁已经被线程释放了同时会唤醒线程等待队列中的下一个线程。
protected final boolean tryRelease(int releases) {int c getState() - releases;//判断是否为当前线程在调用不是抛出IllegalMonitorStateException异常if (Thread.currentThread() ! getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free false;//c 0,释放该锁同时将当前所持有线程设置为nullif (c 0) {free true;setExclusiveOwnerThread(null);}//设置statesetState(c);return free;}private void unparkSuccessor(Node node) {int ws node.waitStatus;if (ws 0)compareAndSetWaitStatus(node, ws, 0);Node s node.next;if (s null || s.waitStatus 0) {s null;// 从后往前找到离head最近而且waitStatus 0 的节点// 其实在ReentrantLock中waitStatus应该只能为0和-1需要唤醒的都是-1(Node.SIGNAL)for (Node t tail; t ! null t ! node; t t.prev)if (t.waitStatus 0)s t;}if (s ! null) LockSupport.unpark(s.thread);// 唤醒挂起线程
}重点unlock最好放在finally中因为如果没有使用finally来释放Lock那么相当于启动了一个定时炸弹如果发生错误我们很难追踪到最初发生错误的位置因为没有记录应该释放锁的位置和时间这也就是 ReentrantLock 不能完全替代 synchronized 的原因因为当程序执行控制离开被保护的代码块时不会自动清除锁。
3.2.2 公平锁
FairSync相对来说就简单很多只有重写的两个方法跟NonfairSync不同
inal void lock() {acquire(1);
}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;
}
3.2.2、公平锁和非公平锁的区别
锁的公平性是相对于获取锁的顺序而言的。如果是一个公平锁那么锁的获取顺序就应该符合请求的绝对时间顺序也就是FIFO线程获取锁的顺序和调用lock的顺序一样能够保证老的线程排队使用锁新线程仍然排队使用锁。非公平锁只要CAS设置同步状态成功则表示当前线程获取了锁线程获取锁的顺序和调用lock的顺序无关全凭运气也就是老的线程排队使用锁但是无法保证新线程抢占已经在排队的线程的锁。ReentrantLock默认使用非公平锁是基于性能考虑公平锁为了保证线程规规矩矩地排队需要增加阻塞和唤醒的时间开销。如果直接插队获取非公平锁跳过了对队列的处理速度会更快。