自旋锁(Ticket,CLH,MCS)
2016-12-14 14:08
716 查看
目录
目录自旋锁Spin lock
代码简单实现
Ticket Spinlock
代码简单实现
CLH锁CLH Spinlock
代码简单实现
CLH分析
MCS锁MCS Spinlock
代码简单实现
自旋锁(Spin lock)
自旋锁(spin lock)是一个典型的对临界资源的互斥手段,它的名称来源于它的特性。自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。由于自旋锁只不进行线程状态的改变,所以当线程竞争不激烈时,它的响应速度极快。自旋锁适用于锁保护的临界区很小的情况,如果线程之间竞争激烈,上下文切换将耗费大量资源,性能会明显下降。代码简单实现
package aqs; import java.util.concurrent.atomic.AtomicReference; public class SpinLock { //使用原子类来标识线程是否获取到了锁 private AtomicReference<Thread> lockOwner = new AtomicReference<Thread>(); public void lock() { Thread currentThread = Thread.currentThread(); // 如果锁未被占用,则设置当前线程为锁的拥有者 while (!lockOwner.compareAndSet(null, currentThread)) { } } public void unlock() { Thread currentThread = Thread.currentThread(); // 只有锁的拥有者才能释放锁 lockOwner.compareAndSet(currentThread, null); } }
这是一个简单的自旋锁代码,compareAndSet保证了我们操作的原则性,可以将c它看做是对锁的获取,只有获取到锁才能继续进行以后的操作,否则将会一直循环检测锁是否被释放,不会阻塞当前线程。
这种自旋锁在多线程同时竞争锁时,并不能保证其公平性。而公平自选锁的三种常见形式就是Ticket CLH 和MCS。
Ticket Spinlock
Ticket就类似于现实中的排队叫号,锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。代码简单实现
public class TicketSpinLock { // 排队号 private AtomicInteger ticketNum = new AtomicInteger(0); // 服务号 private AtomicInteger owner = new AtomicInteger(0); // 当前线程持有的票据 private static final ThreadLocal<Integer> myTicketLocal = new ThreadLocal<Integer>(); public void lock() { int myTicket = ticketNum.getAndIncrement();// 票据 myTicketLocal.set(myTicket); while (myTicket != owner.get()) { } } public void unlock() { int myTicket = myTicketLocal.get(); owner.compareAndSet(myTicket, myTicket + 1); } public static void main(String[] args) throws InterruptedException { TicketSpinLock lock = new TicketSpinLock(); Service service = new Service(new TicketSpinLock()); for (int i = 0; i < 100; i++) { new Thread(new ServiceThread(service), i + "").start(); } TimeUnit.SECONDS.sleep(20); } public static class ServiceThread extends Thread { private Service service; public ServiceThread(Service service) { this.service = service; } @Override public void run() { service.doService2(); // service.doService(); } } // dosomething public static class Service { private int i; private TicketSpinLock ticketLock; public Service() { } public Service(TicketSpinLock ticketLock) { this.ticketLock = ticketLock; } // 加锁 public void doService() { ticketLock.lock(); i = i + 1; System.out.println(String.format("i=%s", i)); ticketLock.unlock(); } // 不加锁 public void doService2() { i = i + 1; System.out.println(String.format("i=%s", i)); } } }
Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。下面介绍的CLH锁和MCS锁都是为了解决这个问题的,它们不再在一个全局变量上自旋,而是在自身本地变量上自旋。
CLH锁(CLH Spinlock)
CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。当一个线程需要获取锁时: a.创建一个的QNode,将其中的locked设置为true表示需要获取锁 b.线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋结点的引用myPred c.该线程就在前趋结点的locked字段上旋转,直到前趋结点释放锁 d.当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前趋结点
如下图,线程A需要获取锁,其myNode域为true,tail指向线程A的结点,然后线程B也加入到线程A后面,tail指向线程B的结点。然后线程A和B都在其myPred域上旋转,一旦它的myPred结点的locked字段变为false,它就可以获取锁。明显线程A的myPred locked域为false,此时线程A获取到了锁。
bd03
代码简单实现
public class CLHLock implements Lock { AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode()); ThreadLocal<QNode> myPred; ThreadLocal<QNode> myNode; public CLHLock() { tail = new AtomicReference<QNode>(new QNode()); myNode = new ThreadLocal<QNode>() { protected QNode initialValue() { return new QNode(); } }; myPred = new ThreadLocal<QNode>() { protected QNode initialValue() { return null; } }; } @Override public void lock() { QNode qnode = myNode.get(); qnode.locked = true; QNode pred = tail.getAndSet(qnode); myPred.set(pred); while (pred.locked) { } } @Override public void unlock() { QNode qnode = myNode.get(); qnode.locked = false; myNode.set(myPred.get()); } }
CLH分析
CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个。myNode,L个锁有L个tail),CLH的一种变体被应用在了JAVA并发框架中。CLH在SMP系统结构下该法是非常有效的。但在NUMA系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣,一种解决NUMA系统结构的思路是MCS队列锁。
MCS锁(MCS Spinlock)
MSC与CLH最大的不同并不是链表是显示还是隐式,而是线程自旋的规则不同:CLH是在前趋结点的locked域上自旋等待,而MSC是在自己的结点的locked域上自旋等待。正因为如此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。MCS队列锁的具体实现如下: a. 队列初始化时没有结点,tail=null b. 线程A想要获取锁,于是将自己置于队尾,由于它是第一个结点,它的locked域为false c. 线程B和C相继加入队列,a->next=b,b->next=c。且B和C现在没有获取锁,处于等待状态,所以它们的locked域为true,尾指针指向线程C对应的结点 d. 线程A释放锁后,顺着它的next指针找到了线程B,并把B的locked域设置为false。这一动作会触发线程B获取锁
代码简单实现
public class CLHLock implements Lock { AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode()); ThreadLocal<QNode> myPred; ThreadLocal<QNode> myNode; public CLHLock() { tail = new AtomicReference<QNode>(new QNode()); myNode = new ThreadLocal<QNode>() { protected QNode initialValue() { return new QNode(); } }; myPred = new ThreadLocal<QNode>() { protected QNode initialValue() { return null; } }; } @Override public void lock() { QNode qnode = myNode.get(); qnode.locked = true; QNode pred = tail.getAndSet(qnode); myPred.set(pred); while (pred.locked) { } } @Override public void unlock() { QNode qnode = myNode.get(); qnode.locked = false; myNode.set(myPred.get()); } }
相关文章推荐
- 自旋锁 Spin Lock, Ticket Spin Lock, MCS Spin Lock, CLH Spin Lock
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁
- 自旋锁,排队自旋锁,clh 锁,mcs 锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 【转】自旋锁、排队自旋锁、MCS锁、CLH锁
- [转]自旋锁、排队自旋锁、MCS锁、CLH锁
- java并发笔记之自旋锁、排队自旋锁、MCS队列锁、CLH队列锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- 自旋锁、排队自旋锁、MCS锁、CLH锁
- JAVA多线程之——自旋锁、CLH、MCS
- JAVA多线程之——自旋锁、CLH、MCS
- 【Java】CLH 自旋锁
- 高性能自旋锁 MCS Spinlock 的设计与实现
- JAVA多线程之——自旋锁、CLH、MCS
- JAVA多线程之——自旋锁、CLH、MCS