互斥锁和自旋锁
2016-03-25 18:56
253 查看
参考
互斥锁:若线程被阻塞后,会释放掉自己所占用的锁,进行上下文切换,不会产生cpu空转的情况。
自旋锁:若线程被阻塞后,会一直占用自己所占的锁,不断占用cpu的时间。
互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。
如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。
如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。
如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。
在自旋锁中 另有三种常见的锁形式:
TicketLock,CLHlock 和MCSlock
每次都要查询一个serviceNum 服务号,影响性能(必须要到主内存读取,并阻止其他cpu修改)。
CLHLock 和MCSLock 则是两种类型相似的公平锁,采用链表的形式进行排序。
从代码上 看,CLH 要比 MCS 更简单,
CLH 的队列是隐式的队列,没有真实的后继结点属性。
MCS 的队列是显式的队列,有真实的后继结点属性。
JUC ReentrantLock 默认内部使用的锁 即是 CLH锁(有很多改进的地方,将自旋锁换成了阻塞锁等等)
互斥锁:若线程被阻塞后,会释放掉自己所占用的锁,进行上下文切换,不会产生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锁(有很多改进的地方,将自旋锁换成了阻塞锁等等)
相关文章推荐
- maven命令
- 通过显式Intent启动另一个Activity
- StringUtils的isBlank与isEmply详解
- IT十八掌作业_java基础第十一天_集合
- 十进制的转换
- 使用 策略模式 控制用户的登录与否的行为
- 递归
- GitHub机器学习项目,你了解多少?
- GitHub深度学习项目,你了解多少?
- 网络流入门—用于最大流的Dinic算法
- 最简单的二进制神经网络
- Git命令使用个人总结
- TOP排行新闻列表。
- 纠正对Fragment Transaction BackStack的误解
- mybatis中mapping文件like查询方式
- Java Learning:并发中的同步锁(synchronized)
- 求整数组里最大子数组的和
- 简单的logback日志收集服务端
- 找到⼀个未知长度单链表的倒数第K个节点
- Struts2 - 常用的constant总结