一、什么是CAS

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

在JAVA中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。

二、CAS典型应用

java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的(eg. AtomicInteger.java,AtomicBoolean,AtomicLong)。下面以 AtomicInteger.java 的部分实现来大致讲解下这些原子类的实现。

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe(); private volatile int value;// 初始int大小
// 省略了部分代码... // 带参数构造函数,可设置初始int大小
public AtomicInteger(int initialValue) {
value = initialValue;
}
// 不带参数构造函数,初始int大小为0
public AtomicInteger() {
} // 获取当前值
public final int get() {
return value;
} // 设置值为 newValue
public final void set(int newValue) {
value = newValue;
} //返回旧值,并设置新值为 newValue
public final int getAndSet(int newValue) {
/**
* 这里使用for循环不断通过CAS操作来设置新值
* CAS实现和加锁实现的关系有点类似乐观锁和悲观锁的关系
* */
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
} // 原子的设置新值为update, expect为期望的当前的值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} // 获取当前值current,并设置新值为current+1
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
} // 此处省略部分代码,余下的代码大致实现原理都是类似的
}

一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized 关键字的方式高效的多(查看getAndSet(),可知如果资源竞争十分激烈的话,这个for循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。 
在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。通常第一映像可能就是:

public class Counter {
private int count;
public Counter(){}
public int getCount(){
return count;
}
public void increase(){
count++;
}
}

上面这个类在多线程环境下会有线程安全问题,要解决这个问题最简单的方式可能就是通过加锁的方式,调整如下:


public class Counter {
private int count;
public Counter(){}
public synchronized int getCount(){
return count;
}
public synchronized void increase(){
count++;
}
}

这类似于悲观锁的实现,我需要获取这个资源,那么我就给他加锁,别的线程都无法访问该资源,直到我操作完后释放对该资源的锁。我们知道,悲观锁的效率是不如乐观锁的,上面说了Atomic下的原子类的实现是类似乐观锁的,效率会比使用 synchronized 关系字高,推荐使用这种方式,实现如下:

public class Counter {
private AtomicInteger count = new AtomicInteger();
public Counter(){}
public int getCount(){
return count.get();
}
public void increase(){
count.getAndIncrement();
}
}  

三、什么是AQS

AQS(AbstractQueuedSynchronizer),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。它使用了一个原子的int value status来作为同步器的状态(如:独占锁,1代表已占有,0代表未占有),通过该类提供的原子修改status方法(getState setState and compareAnsSetState),我们可以把它作为同步器的基础框架类来实现各种同步器。AQS还定义了一个实现了Condition接口的ConditionObject内部类。Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。 
简单来说,就是Condition提供类似于Object的wait、notify的功能signal和await,都是可以使一个正在执行的线程挂起(推迟执行),直到被其他线程唤醒。但是Condition更加强大,如支持多个条件谓词、保证线程唤醒的顺序和在挂起时不需要拥有锁。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(eg. Semaphore)

四、AQS用法

如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)

To use this class as the basis of a synchronizer, redefine the
* following methods, as applicable, by inspecting and/or modifying
* the synchronization state using {@link #getState}, {@link
* #setState} and/or {@link #compareAndSetState}:
*
* <ul>
* <li> {@link #tryAcquire}
* <li> {@link #tryRelease}
* <li> {@link #tryAcquireShared}
* <li> {@link #tryReleaseShared}
* <li> {@link #isHeldExclusively}
* </ul>

如JDK的文档中所说,使用AQS来实现一个同步器需要覆盖实现如下几个方法,并且使用getState,setState,compareAndSetState这几个方法来设置获取状态 
1. boolean tryAcquire(int arg) 
2. boolean tryRelease(int arg) 
3. int tryAcquireShared(int arg) 
4. boolean tryReleaseShared(int arg) 
5. boolean isHeldExclusively()

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占(排他)获取锁的同步器应该实现tryAcquire、 tryReleaseisHeldExclusively而支持共享获取的同步器应该实现tryAcquireSharedtryReleaseSharedisHeldExclusively。下面以 CountDownLatch 举例说明基于AQS实现同步器, CountDownLatch 用同步状态持有当前计数,countDown方法调用 release从而导致计数器递减;当计数器为0时,解除所有线程的等待;await调用acquire,如果计数器为0,acquire 会立即返回,否则阻塞。通常用于某任务需要等待其他任务都完成后才能继续执行的情景。源码如下:

public class CountDownLatch {
/**
* 基于AQS的内部Sync
* 使用AQS的state来表示计数count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
// 使用AQS的getState()方法设置状态
setState(count);
} int getCount() {
// 使用AQS的getState()方法获取状态
return getState();
} // 覆盖在共享模式下尝试获取锁
protected int tryAcquireShared(int acquires) {
// 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1
return (getState() == 0) ? 1 : -1;
} // 覆盖在共享模式下尝试释放锁
protected boolean tryReleaseShared(int releases) {
// 在for循环中Decrement count直至成功;
// 当状态值即count为0的时候,返回false表示 signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
} private final Sync sync; // 使用给定计数值构造CountDownLatch
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
} // 让当前线程阻塞直到计数count变为0,或者线程被中断
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} // 阻塞当前线程,除非count变为0或者等待了timeout的时间。当count变为0时,返回true
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
} // count递减
public void countDown() {
sync.releaseShared(1);
} // 获取当前count值
public long getCount() {
return sync.getCount();
} public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}  

  

五、AQS独占锁官方例子(1代表锁被占用,0代表未被占)

class Mutex implements Lock, java.io.Serializable {

   // 定义一个内部帮助类
private static class Sync extends AbstractQueuedSynchronizer {
// 判断当前独占锁是否被持有
protected boolean isHeldExclusively() {
return getState() == 1;
} // 重写AQS独占锁获取锁方法:如果当前AQS的state状态为0,则获取锁成功
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
//CAS (Compare and Set)调用CPU指令原子更新AQS的state值
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
//返回获取成功
return true;
}
return false;
} // 重写AQS独占锁释放方法
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
//可以看到这里并没有使用CAS原子更新state的值,不够严谨
setState(0);
return true;
} // 提供条件队列功能
Condition newCondition() { return new ConditionObject(); } // Deserialize properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
} // 下面Mutex提供的功能,实际上仅仅只是委托了AQS的独占锁实现类Sync
private final Sync sync = new Sync(); public void lock() { sync.acquire(1); }
public boolean tryLock() { return sync.tryAcquire(1); }
public void unlock() { sync.release(1); }
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
} /*
看完了上面AQS的独占锁实现,再来看下AQS的共享锁实现。下面这个是AQS的共享锁实现类,它类似于JDK提供的闭锁CountDownLatch
BooleanLatch 顾名思义,是个类似于布尔型的闭锁。即如果该锁为真时候则能获取,为假时候则等待直到另外线程修改该锁状态为真。
*/
class BooleanLatch { private static class Sync extends AbstractQueuedSynchronizer {
boolean isSignalled() { return getState() != 0; }
//重写AQS的共享锁获取方法,只要返回的值大于0则可以获取锁,否则AQS会调用unsafe的park挂起线程,后面我们会分析这块源码
protected int tryAcquireShared(int ignore) {
return isSignalled() ? 1 : -1;
}
//重写AQS的共享锁释放方法,这里仅仅只是设置AQS的state值为1,和参数ignore没有任何关系。设置完毕后,AQS会调用unsafe的unpark唤醒线程,则之前被挂起的线程会重新执行tryAcquireShared
//方法。而此时的state大于0,则可以获取锁
protected boolean tryReleaseShared(int ignore) {
setState(1);
return true;
}
} private final Sync sync = new Sync();
public boolean isSignalled() { return sync.isSignalled(); }
public void signal() { sync.releaseShared(1); }
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
}

AQS就是一个阻塞锁和相关同步器的实现类的底层框架!它封装了线程、挂起唤醒的内部运作,我们只需要通过设置AQS的state值来控制他的线程挂起和唤醒的运作路径,就可以实现我们的同步器类了。  

六、LoclSupport源码分析

LockSupport是用来创建锁和其他同步类的基本线程阻塞基本体(primitives)。通过调用LockSupport的park方法可以申请一个许可,如果当前许可可用的话,那么则立即返回,否则则阻塞等待许可。直到另外一个线程调用unpark方法对被阻塞的线程的许可进行释放。(默认许可阻塞)

park方法还支持Blocker对象参数,在调用park方法对当前线程进行阻塞时候,可以把Blocker对象参数传递过来,LockSupport的park方法里会调用setBlocker方法把该对象参数存入到Thread里的parkBlocker变量里。我们可以通过getBlocker获取线程的parkBlocker变量,从而得知该线程正在被阻塞的原因。

public class LockSupport {
private LockSupport() {} // 禁止创建对象 // Unsafe实际上调用的都是本地方法native
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset; static {
try {
//获取Thread的成员变量parkBlocker的偏移量
parkBlockerOffset = unsafe.objectFieldOffset
(java.lang.Thread.class.getDeclaredField("parkBlocker"));
} catch (Exception ex) { throw new Error(ex); }
}
//设置Blocker对象到Thread对象里面
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
unsafe.putObject(t, parkBlockerOffset, arg);
} //获取先前park时候存入的线程的Blocker,如果未存入或者当前线程并不在park里,则为null
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return unsafe.getObjectVolatile(t, parkBlockerOffset);
} //对于给定的Thread释放它所占有的许可。如果当前线程已经占有许可,则释放。如果为占有也释放,那么下次调用该线程的unpark方法时候
//会导致park直接获取了。因为上次已经释放了
public static void unpark(Thread thread) {
if (thread != null)
unsafe.unpark(thread);
} //挂起当前线程,且把Blocker存到线程中
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
//false代表非绝对时间(即相对时间)
unsafe.park(false, 0L);
setBlocker(t, null);
}
}

 

七、LockSupport使用例子

class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue waiters = new ConcurrentLinkedQueue(); public void lock() {
boolean wasInterrupted = false;
//首先获取当前线程,然后存入waiters 队列头部
Thread current = Thread.currentThread();
waiters.add(current); // 先循环判断是否满足线程挂起条件
while (waiters.peek() != current ||
!locked.compareAndSet(false, true)) {
//把当前的FIFOMutex 对象传递到 LockSupport.park(this),LockSupport会把this给赋值到当前Thread的变量里
LockSupport.park(this);
if (Thread.interrupted()) // ignore interrupts while waiting
wasInterrupted = true;
}
//上面那段while循环主要是负责阻塞挂起线程。当挂起条件不满足后则跳出循环
//删除队列头部已被阻塞完成的线程
waiters.remove();
if (wasInterrupted) // reassert interrupt status on exit
current.interrupt();
}
//释放线程
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}} //TestFIFOMutex是FIFOMutex的测试例子
import java.util.Date; public class TestFIFOMutex { private static FIFOMutex mutex = new FIFOMutex(); public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
sleep(1000 * 2);
System.out.println(new Date().toGMTString()+" :t1 准备lock");
mutex.lock();
System.out.println(new Date().toGMTString()+" :t1 完成lock");
}
}).start(); new Thread(new Runnable(){
@Override
public void run() {
sleep(1000 * 3);
System.out.println(new Date().toGMTString()+" :t2 准备lock");
mutex.lock();
System.out.println(new Date().toGMTString()+" :t2 完成lock");
}
}).start(); new Thread(new Runnable(){
@Override
public void run() {
sleep(1000 * 5);
System.out.println(new Date().toGMTString()+" :t3 准备 unlock释放");
mutex.unlock();
System.out.println(new Date().toGMTString()+" :t3 释放完毕unlock");
}
}).start();
} public static void sleep(long num){
try {
Thread.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
} } //TestFIFOMutex执行结果
24 Nov 2017 14:33:46 GMT :t1 准备lock
24 Nov 2017 14:33:46 GMT :t1 完成lock
24 Nov 2017 14:33:47 GMT :t2 准备lock
24 Nov 2017 14:33:49 GMT :t3 准备 unlock释放
24 Nov 2017 14:33:49 GMT :t2 完成lock
24 Nov 2017 14:33:49 GMT :t3 释放完毕unlock  

 

可以发现t2完成lock必须等到t3进行unlock,因为t2线程已经给part挂起了,必须等待unpart唤醒。

八、AQS链表队列结构

AQS内部维护了一个以Node为节点实现的链表的队列。

/**
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
/**
static final class Node {
//SHARED作为共享模式下的常量
static final Node SHARED = new Node();
//EXCLUSIVE作为独占模式下的常量
static final Node EXCLUSIVE = null;
//常量:表示节点的线程是已被取消的
static final int CANCELLED = 1;
//常量:表示当前节点的后继节点的线程需要被唤醒
static final int SIGNAL = -1;
//常量:表示线程正在等待某个条件
static final int CONDITION = -2;
//常量:表示下一个共享模式的节点应该无条件的传播下去
static final int PROPAGATE = -3;
/**
* 状态字段:只具有以下值
* SIGNAL: 当前节点的后继节点已经 (或即将)被阻塞(通过park) , 所以当 当前节点释放或则被取消时候
* ,一定要unpark它的后继节点。为了避免竞争,获取方法一定要首先设置node为signal,然后再次重
* 新调用获取方法,如果失败,则阻塞
* CANCELLED: 当前节点由于超时或者被中断而被取消。一旦节点被取消后,那么它的状态值不在会被改变,且当前节点的线程不会再次被阻塞
* CONDITION: 表示当前节点正在条件队列(AQS下的ConditionObject里也维护了个队列)中,
* 在从conditionObject队列转移到同步队列前,它不会在同步队列(AQS下的队列)中被使用。
* 当成功转移后,该节点的状态值将由CONDITION设置为0
* PROPAGATE: 共享模式下的释放操作应该被传播到其他节点。该状态值在doReleaseShared方法中被设置的,
*
* 0: 以上都不是
*
* 该状态值为了简便使用,所以使用了数值类型。非负数值意味着该节点不需要被唤醒。所以,大多数代码中不需要检查该状态值的确定值,
* 只需要根据正负值来判断即可对于一个正常的Node,他的waitStatus初始化值时0.
对于一个condition队列中的Node,他的初始化值时CONDITION
如果想要修改这个值,可以使用AQS提供CAS进行修改
*/
volatile int waitStatus;
/**
* 指向当前节点的前驱节点,当前节点依赖前驱节点来检测waitStatus,前驱节点是在当前节点入队时候被设置的。
* 为了提高GC效率,在当前节点出队时候会把前驱节点设置为null。而且,在取消前驱节点中,则会循环直到找到一个非取消的节点,
* 由于头节点永远不会是取消状态,所以一定能找到。
*/
volatile Node prev;
/**
* 指向当前节点的后继节点,在当前节点释放时候会唤醒后继节点。该后继节点也是在入队时候被分配的。
* 当前驱节点被取消时候,会重新调整链表的节点链接指向关系。如:前驱节点的前驱节点指向当前节点。
* 且把前驱节点设置为null。节点入队操作过程完成前,入队操作并还未设置前驱节点的后继节点。所以
* 会看到前驱节点的后继节点为null,但是这并不意味着前驱节点就是队列的尾节点!如果后继节点为null,
* 我们可以通过从尾节点向前扫描来做双重检测。一个被取消的节点的后继节点被设置为自身。即node.next=node。
* 这样设置会帮助isOnSyncQueue的执行效率更高(即执行时间更短。注意该方法的if (node.next != null))
*/
volatile Node next;
/**
* 当前节点的线程。在构造Node时候被初始化,在节点使用完毕后设置为null
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* ConditionObject链表的后继节点或者代表共享模式的节点SHARED。Condition条件队列:因为Condition队列只能在独占模式下被能被访问,
* 我们只需要简单的使用链表队列来链接正在等待条件的节点。再然后它们会被转移到同步队列(AQS队列)再次重新获取。
* 由于条件队列只能在独占模式下使用,所以我们要表示共享模式的节点的话只要使用特殊值SHARED来标明即可。
*/
Node nextWaiter;
/**
* 如果节点是属于共享模式节点则返回true
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}

公共辅助方法 
在node获取失败时,node入队后,检测和更新node的状态值。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//该节点如果状态如果为SIGNAL。则返回true,然后park挂起线程
if (ws == Node.SIGNAL)
return true;
//表明该节点已经被取消,向前循环重新调整链表节点
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //执行到这里代表节点是0或者PROPAGATE,然后标记他们为SIGNAL,但是
//还不能park挂起线程。需要重试是否能获取,如果不能则挂起
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

挂起当前线程,且返回线程的中断状态

private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

取消节点

private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
//迭代剔除已被取消的节点
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws; if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
/**
1.头部head
2.当前节点的前驱节点状态为SIGNAL + 前驱节点线程不为null
3.如果前驱节点不是取消状态且修改前驱节点状态为SIGNAL成功 + 前驱节点线程不为null 以上三总情况则会释放当前取消的节点的后继节点的线程(注意仅仅只是释放线程,并不代表能成功出队列。还需要在for(;;)重试truAcquire*)。 **/
unparkSuccessor(node);
}
node.next = node; // help GC
}
}  

九、独占模式public final void acquire(int arg) 和public final boolean release(int arg)

获取方法

/**
* 忽略中断的(即不手动抛出InterruptedException异常)独占模式下的获取方法。该方法在成功返回前至少
* 会调用一次tryAcquire()方法(该方法是子类重写的方法,如果返回true则代表能成功获取).否则当前线程会进入
* 队列排队,重复的阻塞和唤醒等待再次成功获取后返回, 该方法可以用来实现Lock.lock
*
* @param arg 这个值被传递给tryAcquire使用,主要是用来作为state值处理的参数。可以根据需要灵活使用该值
*/
public final void acquire(int arg) {
//首先调用tryAcquire(arg)值尝试获取,如果成功则返回true。!true则等于false不需要进入 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//进行排队等待再次成功获取
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} /**
* 尝试在独占模式下获取.这个方法应该查下该对象的状态是否被允许在独占模式下获取,如果是才获取
* 这个方法通常由线程执行获取时调用,如果该方法返回false,且该线程还未进入队列,则该线程会进去AQS队列排队然后挂起线程,
* 直到其他线程调用release进行通知已被的线程释放。该方法可以备用来实现Lock.tryLock
*
*默认抛出UnsupportedOperationException异常
*
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

根据参数mode(Node.EXCLUSIVE或者Node.SHARED)和Thread.currentThread()创建一个节点Node,然后加到AQS链表队列. 
只有在tryAcquire获取失败时候,才进入AQS链表队列等待再次成功获取。 
打个比方:我们去医院看医生,挂号在某个特定教授。如果当前该教授没有任何病人在看病(相等于tryAcquired),那么我们不需要排队就能 
进去看病。否则的话,我们要在门口排队(相当于addWaiter),等待当前在看病的病人出来,出来一个(相当于release)则正在队列 
头部的病人可以进去看病了(大概相当于acquireQueued)。

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速入队,即无竞争条件下肯定成功。如果失败,则进入enq自旋重试入队
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS替换当前尾部。成功则返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(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;
}
}
}
}

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)) {
setHead(node);
//设置前驱节点的后继节点为null。使前驱节点成为不可达。方便GC回收
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断当前节点的线程是否应该被挂起,如果应该被挂起则挂起。等待release唤醒释放
//问题:为什么要挂起当前线程?因为如果不挂起的话,线程会一直抢占着CPU
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
//在队列中取消当前节点
cancelAcquire(node);
}
}

独占模式下的释放,如果方法返回true,可释放1个或者多个线程。该方法可以用来实现Lock.unlock方法


public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

十.共享模式:public final void acquireShared(int arg)和public final boolean releaseShared(int arg)

共享模式的典型例子就是信号量和闭锁了。

共享模式下忽略中断的获取,该方法至少会调用一次子类重写的tryAcquireShared方法,如果首次获取结果大于等于0.则完成获取 
否则进入AQS同步队列阻塞等待机会再次重新尝试获取,直到获取成功。

public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
} /**
* 共享模式下尝试获取。该方法应该确定在共享模式下,Object的状态值是否允许被获取。
* 如果该方法返回结果被认为失败(值<0),则当前线程会进入AQS的同步队列阻塞等待
* 知道其他线程调用release释放。
*
*
* 默认抛出 UnsupportedOperationException异常
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return a negative value on failure; zero if acquisition in shared
* mode succeeded but no subsequent shared-mode acquire can
* succeed; and a positive value if acquisition in shared
* mode succeeded and subsequent shared-mode acquires might
* also succeed, in which case a subsequent waiting thread
* must check availability. (Support for three different
* return values enables this method to be used in contexts
* where acquires only sometimes act exclusively.) Upon
* success, this object has been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if shared mode is not supported
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}

共享模式获取的核心公共方法

private void doAcquireShared(int arg) {
//添加当前线程为一个共享模式的节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
//如果当前节点的前驱节点==head 且 state值大于0则认为获取成功
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//判断当前节点是否应该被阻塞,是则阻塞等待其他线程release
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//如果出异常,没有完成当前节点的出队,则取消当前节点
if (failed)
cancelAcquire(node);
}
}

设置节点node为AQS同步队列的头结点。如果后继节点为共享模式且参数propagate是否大于0或者PROPAGATE是否已被设置,则唤醒后继节点

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//首先设置node为头节点
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/ if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}

共享模式下的释放,唤醒后继节点并且保证传播。 
注意:在独占模式下,如果需要唤醒,仅仅相当于调用头节点的unparkSuccessor动作。  

private void doReleaseShared() {
/*
* 确保释放的传播性, 即使在并发情况下,多个线程在获取、释放。
* 如果需要唤醒,则通常尝试头节点的unparkSuccessor 动作。
* 但是如果他不符合唤醒的条件,为了确保能正确release,那么则把头节点的state设置为
* 的state设置为PROPAGATE。此外,在执行该行为时,为了以防万一有新
* 节点的加入我们的行为必须在循环中,而且如果在修改状态中,如果修改失败,那么
* 也需要重新尝试修改。
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
/**
为什么这里要把state状态修改为Node.PROPAGATE?可以想象一下在什么情况下节点的状态会被修改为0,。
线程1调用doReleaseShared的方法释放头节点,此时头节点的状态被设置为0,compareAndSetWaitStatus(h, Node.SIGNAL, 0)
然后unparkSuccessor(h); AQS的头节点则被唤醒重试尝试出队。注意:此时的头节点状态为0!!
线程2调用且成功进入到doReleaseShared方法,此时获取头节点状态为0(新的头节点还未被setHead),既然能进入到这里,总不能释放失败吧?
然后则把头节点由0修改为Node.PROPAGATE,这样我们在关注下setHeadAndPropagate方法
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
可以看到这时候h.waitStatus是小于0的。则保证了并发情况下线程2的释放成功!
**/
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
/**
为什么这里要加个h == head?
思考什么情况下这里的头部会被改变。上面也说了:Additionally, we must loop in case a new node is addedwhile we are doing this
假设当前AQS队列没有任何等待的节点,即head==tail。这时候上面的if判断不成立,执行到这里适合再次判断h==head,如果有新节点添加
进来,则h!=head,会重新尝试释放。我结论:应该是为了保证在多线程情况下的尽可能成功性。
**/
if (h == head) // loop if head changed
break;
}
}

十一.区别 Shard(共享模式) 和非Shard(独占模式)

虽然AQS的共享模式和独占模式写的有点复杂。但是要知道无非就两种情况: 
独占模式:即state值在0和1之前来回切换,保证同一时间只能有一个线程是处于活动的,其他线程都被阻塞, 参考:ReentranLock.. 
共享模式:state值在整数区间内,如果state值<0则阻塞,否则则不阻塞。可以参考:Semphore、CountDownLautch..

十二.ConditionObject

Condition内部类是作为锁实现的一种基础服务。 Condition内部类实现类Condition接口。 
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
//条件队列的头节点
private transient Node firstWaiter;
//条件队列的尾节点
private transient Node lastWaiter;
/**
* Creates a new <tt>ConditionObject</tt> instance.
*/
public ConditionObject() { }
// Internal methods /**
添加一个新节点到条件队列
可以看到,在修改队列节点结构时候并没有使用CAS,这是因为通常使用condition的前提必须是在独占模式的lock下。
**/
private Node addConditionWaiter() {
Node t = lastWaiter;
//如果条件队列的尾节点已被取消,则调用unlinkCancelledWaiters重新调整队列结构
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
} /**
删除condition等待队列中的节点first,且把节点first转移到condition所属的AQS等待队列中,
直到成功。 **/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
} /**
转移且删除所有的节点
**/
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
} /**
持有锁的情况下,从condition等待队列中分离已被取消的节点。
该方法只有在条件队列中发生节点取消或者添加一个新的节点的时候发现尾节点已被取消时调用。
该方法需要避免垃圾滞留(没有signal时候),所以即使它需要完整遍历,但也只有在在由于没有signal
而导致的超时或者取消时才起作用。 It traverses all nodes rather than stopping at a
particular target to unlink all pointers to garbage nodes without requiring many re-traversals
during cancellation storms.
**/
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
// public methods /**
在独占锁模式下,删除当前Condition中的等待队列的头节点.
**/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
/**
删除当前Condition中的等待队列的头节点,且转移头节点到AQS的同步等待队列中
注意:仅仅只是删除头节点,并没有唤醒任何节点!
那么疑问来了,为什么signal不唤醒节点却能达到Object的signal一样的效果呢?(头)
单纯的从这一步解释的通,因为signal代表着唤醒线程。AQS利用signal必须得持有独占锁,
在unlock时候实际上就是唤醒await节点。而这里的signal仅仅只是移除等待队列的头部
**/
doSignal(first);
} /**
在当前线程拥有独占锁的情况下,删除当前condition 中等待队列的所有线程
**/
public final void signalAll() {
//判断是否拥有独占锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
/**
不抛出线程中断异常的的条件等待的实现.
保存getState方法返回的状态值,且调用release释放getState方法返回的状态值。
为什么要释放所有的getState的状态值?
试下Condition的await方法功能是类似于Object的await方法,Object的await方法
在调用时候需要进行该Object的同步(Synchronized)。所以Condition的await在
被调用时候也需要拥有独占锁(Lock.lock)。
如果 线程1 await后不释放AQS的state,那么 线程2 在signal时候无法获取锁。 如果release失败,则抛出异常 IllegalMonitorStateException 接着会调用LockSupport.park(this);阻塞当前线程,直到该线程被唤醒 **/
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
//线程被唤醒后需要判断当前节点是否在同步队列(因为调用signal唤醒,会把头节点从等待队列转移到AQS的同步队列 )
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
//这里注意acquireQueued带有一个形参savedState。至于为什么要把这个savedState传入,
//想想之前释放掉的savedState就明白了
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
/*
* For interruptible waits, we need to track whether to throw
* InterruptedException, if interrupted while blocked on
* condition, versus reinterrupt current thread, if
* interrupted while blocked waiting to re-acquire.
*/
/** Mode meaning to reinterrupt on exit from wait */
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE = -1;
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
/** **/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
} /**
根据mode来决定是否抛出InterruptedException异常或者,或者不执行任何动作。
**/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw Interrupte dException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with
* saved state as argument, throwing
* IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 添加一个新节点到等待队列
Node node = addConditionWaiter();
//释放当前所有state。因为其他线程需要lock,后面还会恢复
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断当前节点是否已被释放(从等待队列转移到同步队列)
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//调用acquireQueued使节点node从AQS同步队列出队
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果存在后继节点,则检查且清理取消节点。
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//指定一个相对时间,如果在相对时间内被唤醒且检查是否满足不再阻塞线程条件,否知阻塞直至到达过期时间,
//释放当前线程
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
long lastTime = System.nanoTime();
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
//这里和上面普通。只挂起线程nanosTimeout纳秒
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
long now = System.nanoTime();
//注意这里运算符德优先级关系
nanosTimeout -= now - lastTime;
lastTime = now;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return nanosTimeout - (System.nanoTime() - lastTime);
}
//指定一个绝对时间,如果在绝对时间之前被唤醒,则线程检查是否满足完成阻塞,是则推出阻塞。
//否则继续阻塞直至到达绝对时间,然后才中断阻塞
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
if (deadline == null)
throw new NullPointerException();
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
} public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
if (unit == null)
throw new NullPointerException();
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
long lastTime = System.nanoTime();
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
// support for instrumentation
//判断sync是否和和当前的调用的this是同一个。追踪下AQS的owns(ConditionObject condition)就明白了
final boolean isOwnedBy(AbstractQueuedSynchronizer sync) {
return sync == AbstractQueuedSynchronizer.this;
} //查询当前等待队列是否存在 有效等待(waitStatus 值为CONDITION)的线程
protected final boolean hasWaiters() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
return true;
}
return false;
} //获取当前等待队列里节点数的估计值
protected final int getWaitQueueLength() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int n = 0;
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
++n;
}
return n;
}
//获取当前等待队列里节点里的处于有效等待唤醒状态的线程的集合
protected final Collection<Thread> getWaitingThreads() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION) {
Thread t = w.thread;
if (t != null)
list.add(t);
}
}
return list;
}
}

注:

特别鸣谢:https://blog.csdn.net/wojiaolinaaa/article/details/50070031

https://blog.csdn.net/zcw4237256/article/details/78552741  

  

												

【多线程系列】AQS CAS简单介绍的更多相关文章

  1. Java多线程系列--AQS之 LockSupport

    concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS(JAVA CAS原理.unsafe.AQS)框架借助于两个类: Unsafe(提供CAS操作 ...

  2. Docker系列之原理简单介绍

    目录 1.1.Docker架构简介 1.2.Docker 两个主要部件 1.3.虚拟机和Docker对比: 1.4.Docker内部结构 Docker系列之原理简单介绍 @ Docker是一个开源的应 ...

  3. openresty开发系列10--openresty的简单介绍及安装

    openresty开发系列10--openresty的简单介绍及安装 一.Nginx优点 十几年前,互联网没有这么火,软件外包开发,信息化建设,帮助企业做无纸化办公,收银系统,工厂erp,c/s架构偏 ...

  4. iOS开发多线程篇 09 —NSOperation简单介绍

    iOS开发多线程篇—NSOperation简单介绍 一.NSOperation简介 1.简单说明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现 ...

  5. AJ学IOS(50)多线程网络之GCD简单介绍(任务,队列)

    AJ分享,必须精品 GCD简单介绍 1.什么是GCD? 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 2.GCD的优势 GCD是苹果 ...

  6. 初学HTML5系列一:简单介绍

    最近很闲,就想着学点东西,然后就瞄中了html5,以前只看过很简单的一些,这次是系统的学下,顺便也记录下.废话不多说,开始正题. 稍微介绍下html5,html5是W3C和WHATWG 合作的结果. ...

  7. iOS:多线程同步加锁的简单介绍

    多线程同步加锁主要方式有3种:NSLock(普通锁).NSCondition(状态锁).synchronized同步代码块 还有少用的NSRecursiveLock(递归锁).NSConditionL ...

  8. iOS多线程篇:NSThread简单介绍和使用

    一.什么是NSThread NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程, 需要手动管理线程的生命周期,处理线程同 ...

  9. 【Zookeeper系列】Zookeeper简单介绍(转)

    原文链接:https://www.cnblogs.com/sunddenly/p/4033574.html 一.分布式协调技术 在给大家介绍ZooKeeper之前先来给大家介绍一种技术——分布式协调技 ...

随机推荐

  1. android位移动画的两种实现方式

    在android开发,我们会常常使用到位移动画,普通情况下位移动画有两种实现方式.一种是直接通过java代码去实现,第二种是通过配置文件实现动画,以下是两种动画的基本是用法: 纯Java代码实现: / ...

  2. C#跳转网页7种方法

    1.Response.Redirect(http://www.baidu.com,false); 目标页面和原页面可以在2个服务器上,可输入网址或相对路径.后面的bool值为是否停止执行当前页. 跳转 ...

  3. ssh命令详解3

    SSH 的详细使用方法如下: ssh [-l login_name] [hostname | user@hostname] [command] ssh [-afgknqtvxCPX246] [-c b ...

  4. Drools 语法

    Drools 语法 规则语法 package: package 的名字是随意的,不必必须对应物理路径 import: 导入外部变量 规则的编译与运行要通过Drools 提供的各种API 来实现.API ...

  5. git基于历史commit创建分支

    基于以前的commit创建一个分支 步骤: 1.确定需要取出版本的commit值 git log 2.基于该commit创建分支 git branch <branch name> < ...

  6. 【转】Gulp入门基础教程

    Gulp入门基础教程 原文在此 前言最近流行前端构建工具,苦于之前使用Grunt,代码很难阅读,现在出了Gulp, 真是摆脱了痛苦.发现了一篇很好的Gulp英文教程,整理翻译给大家看看. 为什么使用G ...

  7. hibernate validate

    http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/  --hibernate validate htt ...

  8. 简要介绍DES、RSA MD5 sha1 四种加密算法的优缺点,以及使用场合

    美国数据加密标准(DES)是对称密码算法,就是加密密钥能够从解密密钥中推算出来,反过来也成立.密钥较短,加密处理简单,加解密速度快,适用于加密大量数据的场合.RSA是非对称算法,加密密钥和解密密钥是不 ...

  9. Innodb表空间

    Innodb有两种管理表空间的方法 独立表空间:每一张表都会生成独立的文件来进行存储,每一张表都有一个.frm表描述文件,和一个.ibd文件.其中ibd文件包括了单独一个表的数据内容和索引内容. 共享 ...

  10. python中的字符串常量,是否支持通过下标的方式赋值

    说明: 今天在看python,通过下标获取字符串常量的字符,在想是否可以通过下标的方式赋值. 操作: 1.对字符串下标赋值 >>> text='python' >>> ...