线程概述
线程属性
线程状态
线程监控
线程api
java 多线程实现
多线程优势
多线程挑战
线程安全
- 原子性
- 可见性
- 有序性
保护线程安全的手段
锁
内部锁
外部锁
锁怎么保证原子性,可见性,有序性
volatile 关键字
static 和final 关键字
java 内部锁原理
java 外部锁原理(aqs)
概述
AQS(AbstractQueuedSynchronizer)是用来构建锁和其他同步组件的基础框架,它也是Java三大并发工具类(CountDownLatch、CyclicBarrier、Semaphore)的基础。AQS可以被当做是一个同步监视器的实现,并且具有排队功能。当线程尝试获取AQS的锁时,如果AQS已经被别的线程获取锁,那么将会新建一个Node节点,并且加入到AQS的等待队列中,这个队列也由AQS本身自己维护。当锁被释放时,唤醒下一个节点尝试获取锁。
数据结构
AQS
//头结点
private transient volatile Node head;
//尾结点
private transient volatile Node tail;
//锁状态,更新通过CAS(有ABA问题)
private volatile int state;
Node
//当前节点处于共享模式的标记
static final Node SHARED = new Node();
//当前节点处于独占模式的标记
static final Node EXCLUSIVE = null;
//线程被取消了
static final int CANCELLED = 1;
//释放资源后需唤醒后继节点
static final int SIGNAL = -1;
//等待condition唤醒
static final int CONDITION = -2;
//工作于共享锁状态,需要向后传播,
//比如根据资源是否剩余,唤醒后继节点
static final int PROPAGATE = -3;
//等待状态,有1,0,-1,-2,-3五个值。分别对应上面的值
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
//等待锁的线程
volatile Thread thread;
//等待条件的下一个节点,ConditonObject中用到
Node nextWaiter;
关键方法
- 获取资源
//获取资源,子类实现。如ReentrantLock 实现公平锁和非公平锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
- 释放资源
// 释放资源,子类实现
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
- 获取共享资源,
//获取共享资源,子类实现
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
- 释放共享资源
//释放共享资源,子类实现
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
- 独占锁
//获取资源,已实现,提供子类调用。模板方法,定义程序骨架
public final void acquire(int arg) {
//tryAcquire 子类实现的方法
if (!tryAcquire(arg) &&
//addWaiter 尾插法插入节点
//acquireQueued,锁竞争优化
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//中断
selfInterrupt();
}
**aquire的步骤:**\
1)tryAcquire()尝试获取资源。
2)如果获取失败,则通过addWaiter(Node.EXCLUSIVE), arg)方法把当前线程添加到等待队列队尾,并标记为独占模式。
3)插入等待队列后,并没有放弃获取资源,acquireQueued()自旋尝试获取资源。根据前置节点状态状态判断是否应该继续获取资源。如果前驱是头结点,继续尝试获取资源;
4)在每一次自旋获取资源过程中,失败后调用shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()。若返回true,则调用parkAndCheckInterrupt()中断当前节点中的线程。若返回false,则接着自旋获取资源。当acquireQueued(Node,int)返回true,则将当前线程中断;false则说明拿到资源了。
5)在进行是否需要挂起的判断中,如果前置节点是SIGNAL状态,就挂起,返回true。如果前置节点状态为CANCELLED,就一直往前找,直到找到最近的一个处于正常等待状态的节点,并排在它后面,返回false,acquireQueed()接着自旋尝试,回到3)。
6)前置节点处于其他状态,利用CAS将前置节点状态置为SIGNAL。当前置节点刚释放资源,状态就不是SIGNAL了,导致失败,返回false。但凡返回false,就导致acquireQueed()接着自旋尝试。
7)最终当tryAcquire(int)返回false,acquireQueued(Node,int)返回true,调用selfInterrupt(),中断当前线程。
- 锁竞争优化
//锁竞争优化
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前竞争锁的线程Node的前置节点
final Node p = node.predecessor();
//假如前置节点是head,那么表示当前线程是等待队列中最大优先级的等待线程,可以继续最后的尝试获取锁,因为很有可能会获取到锁,从而避免线程挂起、唤醒,耗费资源,这里也算是最终一次尝试获取。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//让线程处于一种自旋状态,
//尝试让该线程重新获取锁!当条件满足获取到了锁则可以从自旋过程中
//退出,否则继续。
//是检查当前是否有必要挂起,前面我们说过,只有当前置节点的waitStatus是`-1`的时候才会挂起当前节点代表的线程。当`shouldParkAfterFailedAcquire(p, node)`返回true的时候,就可以执行`parkAndCheckInterrupt()`来挂起线程。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己
//此时当前节点可以安全的park(),因此返回true
return true;
if (ws > 0) {
//前驱节点的状态是CANCLLED,说明前置节点已经放弃获取资源了
//此时一直往前找,直到找到最近的一个处于正常等待状态的节点
//并排在它后面,返回false
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置
//为SIGNAL,让它释放资源后通知自己
//如果前置节点刚释放资源,状态就不是SIGNAL了,这时就会失败
// 返回false
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//使用LockSupport,挂起当前线程
LockSupport.park(this);
return Thread.interrupted();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
aqs 应用
-
ReentrantLock(独占锁)
概述
ReentrantLock是独占锁,实现公平锁和非公平锁,默认非公平。
- 加锁 默认非公平加锁方式。加锁过程如下:
1)更新锁状态(cas),若更新成功则设置独占
- 更新不成功,则调用AQS中acquire 加锁
3)AQS中acquire 方法调用子类tryAcquire 方法尝试加锁,若加锁不成功则自旋
- 释放锁
syn
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
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;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
非公平锁
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() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
释放锁
//ReentrantLock
public void unlock() {
// 继承父类AQS 释放锁方法
sync.release(1);
}
// AQS
public final boolean release(int arg) {
// 子类tryRelease 方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点。
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
//如果状态为负说明是除CANCEL以外的状态,
//尝试在等待信号时清除。
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//下一个节点为空或CANCELLED状态
//则从队尾往前找,找到正常状态的节点作为之后的继承人
//也就是下一个能拿到资源的节点
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);
}
概述
ReentrantLock 是java 显示锁,ReentrantLock实现公平和非公平二种加锁方式,底层实现依赖AQS 实现
-
CountDownLatch
-
CyclicBarrier
-
Semaphore