您的位置:首页 > 其它

互斥锁和自旋锁

2016-03-25 18:56 253 查看
参考

互斥锁:若线程被阻塞后,会释放掉自己所占用的锁,进行上下文切换,不会产生cpu空转的情况。

自旋锁:若线程被阻塞后,会一直占用自己所占的锁,不断占用cpu的时间。

区别

自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。

互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。

应用区别

如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。

如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。

如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。

如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();

public void lock() {
Thread current = Thread.currentThread();
while (!sign.compareAndSet(null, current)) {
}
}

public void unlock() {
Thread current = Thread.currentThread();
sign.compareAndSet(current, null);
}
}
//      使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。
//      当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。
//      由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。
//      但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。
//      适合使用自旋锁。


在自旋锁中 另有三种常见的锁形式:

TicketLock,CLHlock 和MCSlock

TicketLock

import java.util.concurrent.atomic.AtomicInteger;

public class TicketLock {
private AtomicInteger serviceNum = new AtomicInteger();
private AtomicInteger ticketNum = new AtomicInteger();
private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();

public void lock() {
int myticket = ticketNum.getAndIncrement();
LOCAL.set(myticket);
while (myticket != serviceNum.get()) {
}
}

public void unlock() {
int myticket = LOCAL.get();
serviceNum.compareAndSet(myticket, myticket + 1);
}
}


每次都要查询一个serviceNum 服务号,影响性能(必须要到主内存读取,并阻止其他cpu修改)。

CLHLock 和MCSLock 则是两种类型相似的公平锁,采用链表的形式进行排序。

CLHLock

参考

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class CLHLock {
public static class CLHNode {
private volatile boolean isLocked = true;
}

private volatile CLHNode tail;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater
.newUpdater(CLHLock.class, CLHNode.class, "tail");

public void lock() {
CLHNode node = new CLHNode();
LOCAL.set(node);
CLHNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
while (preNode.isLocked) {
}
preNode = null;
LOCAL.set(node);
}
}

public void unlock() {
CLHNode node = LOCAL.get();
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = false;
}
node = null;
}
}


MCSLock

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class MCSLock {
public static class MCSNode {
volatile MCSNode next;
volatile boolean isLocked = true;
}

private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
private volatile MCSNode queue;
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
.newUpdater(MCSLock.class, MCSNode.class, "queue");

public void lock() {
MCSNode currentNode = new MCSNode();
NODE.set(currentNode);
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (preNode != null) {
preNode.next = currentNode;
while (currentNode.isLocked) {
}
}
}

public void unlock() {
MCSNode currentNode = NODE.get();
if (currentNode.next == null) {
if (UPDATER.compareAndSet(this, currentNode, null)) {
} else {
while (currentNode.next == null) {
}
}
} else {
currentNode.next.isLocked = false;
currentNode.next = null;
}
}
}


从代码上 看,CLH 要比 MCS 更简单,

CLH 的队列是隐式的队列,没有真实的后继结点属性

MCS 的队列是显式的队列,有真实的后继结点属性

JUC ReentrantLock 默认内部使用的锁 即是 CLH锁(有很多改进的地方,将自旋锁换成了阻塞锁等等)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: