自旋锁
一般解决多线程共享资源逻辑一致性问题有两种方式:
互斥锁:发现资源被占用时,阻塞自己直到资源解除占用,然后再次尝试获取锁;
自旋锁:发现资源占用时,一直尝试获取锁,线程不会被挂起,即没有线程调度切换的消耗;
SMP
对称多处理(Symmetrical Multi-Processing),是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。SMP能够保证内存一致性,但这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,可能会导致CPU资源的浪费。常用的PC机就属于这种。
NUMA
非统一内存访问(Non Uniform Memory Access Architecture)技术可以使众多服务器像单一系统那样运转,同时保留小系统便于编程和管理的优点。它将CPU分为CPU模块,每个CPU模块由多个CPU组成,并且具有独立的本地内存、I/O槽口等,模块之间可以通过互联模块相互访问,因为访问远程内存的延时远远超过本地内存,系统性能无法线性增加。
CLH队列锁
(Craig, Landin, Hagersten三位作者的缩写)CLH 队列锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。CLH在SMP系统结构下非常有效。但在NUMA系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣。
当一个线程需要获取锁时,会创建一个QNode,将其中的locked设置为true,表示需要获取锁,myPred表示对其前驱结点的引用。线程通过调用tail域的getAndSet方法,使自己成为队列尾部,同时获取一个指向其前驱结点的引用myPred。然后线程就在前驱结点的locked字段上自旋。直到前驱结点释放锁[前驱节点的locked == false]。当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前驱结点。
Java中的AQS是CLH队列锁的一种变体实现。
public class CLHLockTest implements Lock {
private AtomicReference<QNode> tail = new AtomicReference<>(new QNode());
private ThreadLocal<QNode> preNode = new ThreadLocal<QNode>() {
@Override
protected QNode initialValue() {
return null;
}
};
private ThreadLocal<QNode> node = new ThreadLocal<QNode>() {
@Override
protected QNode initialValue() {
return new QNode();
}
};
@Override
public void lock() {
QNode qNode = node.get();
qNode.locked = true;
QNode pred = tail.getAndSet(qNode);
preNode.set(pred);
while (pred.locked){
}
}
@Override
public void unlock() {
QNode qNode = node.get();
qNode.locked = false;
node.set(preNode.get());
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
private class QNode {
private volatile boolean locked;
}
private static class Chd implements Runnable {
CLHLockTest c = null;
public Chd(CLHLockTest test) {
this.c = test;
}
@Override
public void run() {
try {
c.lock();
SleepTools.ms(100);
System.out.println("线程名称:"+Thread.currentThread().getName());
} finally {
c.unlock();
}
}
}
public static void main(String[] args) {
CLHLockTest lock = new CLHLockTest();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Chd(lock));
thread.setName("第" + i + "个线程");
thread.start();
}
}
}
MCS队列锁
与CLH是在前趋结点的locked域上自旋等待不同的是,MCS是在自己的结点的locked域上自旋等待。因此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。
MCS队列初始化时没有结点:tail=null。当线程A想要获取锁,它的locked域为false。当线程B和C相继加入队列:A->B->C。这时B和C处于等待状态,所以他们的locked域为true。当线程A释放锁后,顺着NEXT指针找到线程B,并把B的locked域设置为false,这时会触发线程B获取锁。
public class MCSLockTest implements Lock {
private AtomicReference<QNode> tail = new AtomicReference<>();
private ThreadLocal<QNode> node = new ThreadLocal<QNode>(){
@Override
protected QNode initialValue() {
return new QNode();
}
};
@Override
public void lock() {
QNode qNode = node.get();
qNode.locked = true;
QNode pre = tail.getAndSet(qNode);
if (null != pre) {
pre.next = qNode;
while (qNode.locked) {
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
QNode qNode = node.get();
if (null == qNode.next) {
if (tail.compareAndSet(qNode, null)) {
return;
}
while (null == qNode.next) {
}
}
qNode.next.locked = false;
qNode.next = null;
}
@Override
public Condition newCondition() {
return null;
}
private class QNode {
private volatile boolean locked;
QNode next = null;
}
private static class Chd implements Runnable {
MCSLockTest c = null;
public Chd(MCSLockTest test) {
this.c = test;
}
@Override
public void run() {
try {
c.lock();
SleepTools.ms(100);
System.out.println("线程名称:"+Thread.currentThread().getName());
} finally {
c.unlock();
}
}
}
public static void main(String[] args) {
MCSLockTest lock = new MCSLockTest();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Chd(lock));
thread.setName("第" + i + "个线程");
thread.start();
}
}
}
_____个人笔记_____((≡^⚲͜^≡))_____欢迎指正_____