LockFree思想

0x01 摘要

近期看一些源码,会有一些注释是LockFree。这到底啥玩意儿?之前我也不知道啊,遂赶紧上网查之,总结了一些东西作为记录,与大家分享。

0x02 LockFree

2.1 LockFree概念

先上一张神图:

由上图可以看出,LockFree程序必须满足三个条件:

  1. 多线程
  2. 共享内存
  3. 不能彼此阻塞(死锁)

具体来说,如果一个程序是LockFree的,则在运行足够长的一段时间内至少一个线程能取得进展。如果一个程序死锁,这个程序内的所有线程都没法取得进展了,这种情况就不能成为LockFree。在LockFree的程序中,如果一个线程被挂起,决不能影响其他线程继续执行,也就是说是非阻塞的。

因为LockFree能保证有一个线程能执行并取得进展,所以在代码不是无限循环的情况下可以保证所有线程最终能完成自己的工作。

java.util.concurrent.atomic包是LockFree思想实现的例子。

2.2 LockFree编程

2.2.1 简介

LockFree编程的特点:

  • LockFree 技术很容易被错误的使用,代码后期的维护中也不容易意识到,所以非常容易引入 Bug,而且这样的 Bug 还非常难定位。
  • LockFree 技术的细节上依赖于内存系统模型、编译器优化、CPU架构等,而这在使用 Lock 机制时是不相关的,所以也增加了理解和维护的难度。

2.2.2 原子读改写指令(atomic RMW)

原子操作不可再分割。

RMW的设计可让执行更复杂的事务操作变成原子的,使得多个写入者尝试对相同内存区域进行修改时,保证一次只能执行一个操作。

在x86/64的CPU架构中,通过CAS方式实现。其他架构各有不同实现方式。

2.2.3 ABA问题

上面提到了CAS,那么就会有ABA问题。
详细内容可以看我之前写的一篇文章:Java-并发-CAS中的ABA章节部分。

2.2.4 顺序一致性模型

顺序一致性内存模型有两大特性

  1. 一个线程内部的所有操作必须按照程序的顺序来执行
  2. (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

2.2.5 代码示例

以Stack为例子,实现一个LockFree的Stack。

这个例子中使用单链表实现栈,有两个方法:

  • push:栈顶方向压入元素
  • pop:栈顶弹出一个元素
2.2.5.1 非LockFree版本
public class SimpleStack<T>
{private class Node<TNode>{public Node<TNode> next;public TNode item;@Overridepublic String toString(){return item.toString();}}private Node<T> head;public SimpleStack(){head = new Node<T>();}/*** 头插法* @param item*/public void push(T item){Node<T> node = new Node<T>();node.item = item;node.next = head.next;head.next = node;}/*** 删除栈顶元素并返回该元素* 没有元素是返回null* @return*/public T pop(){Node<T> node = head.next;if (node == null)return null;head.next = node.next;return node.item;}
}

测试代码如下:

// push1000个元素,然后再多线程中pop元素观察结果
public class SimpleStackTest
{private static final Logger logger = LoggerFactory.getLogger(SimpleStackTest.class);public static void main(String[] args){SimpleStack<Integer> stack = new SimpleStack<Integer>();for (int i = 1; i <= 1000; i++){stack.push(i);}boolean[] poppedItems = new boolean[1001];BlockingQueue workQueue = new LinkedBlockingDeque<>();ExecutorService executorService = new ThreadPoolExecutor(5,10,1, TimeUnit.MINUTES,workQueue, new ThreadPoolExecutor.DiscardPolicy());AtomicInteger count = new AtomicInteger();for(int i = 0 ; i < 10 ; i++){executorService.submit(() -> {for (int j = 0; j < 100; j++) {count.incrementAndGet();int item = stack.pop();if (poppedItems[item] == true) {logger.error("Thread {}: Item {} was popped before!", Thread.currentThread().getName(), item);}else {poppedItems[item] = true;}}});}try {executorService.awaitTermination(5, TimeUnit.SECONDS);}catch (InterruptedException e) {logger.error("an exception happened:", e);}executorService.shutdownNow();logger.info("Done, count={}" , count);}
}

运行结果如下:

2018-12-10 10:45:47.782 ERROR [pool-1-thread-1] demos.lockfree.nolockstackdemo.SimpleStackTest,41 - Thread pool-1-thread-1: Item 878 was popped before!
2018-12-10 10:45:52.786 INFO  [main] demos.lockfree.nolockstackdemo.SimpleStackTest,58 - Done, count=1000

也就是说出现了同一个元素被pop多次的情况!

2.2.5.2 同步锁版本

在push和pop方法上加入了synchronized修饰。

/*** 头插法* @param item*/
public synchronized void push(T item)
{Node<T> node = new Node<T>();node.item = item;node.next = head.next;head.next = node;
}/*** 删除栈顶元素并返回该元素* 没有元素是返回null* @return*/
public synchronized T pop()
{Node<T> node = head.next;if (node == null)return null;head.next = node.next;return node.item;
}

运行结果正确:

2018-12-10 11:10:53.091 INFO  [main] demos.lockfree.simplelockstackdemo.SimpleLockStackTest,59 - Done, count=1000
2.2.5.3 LockFree版本

但是,以上采用的同步锁方式在高并发场景下,大量出现线程竞争的情况,效率并不高。

下面采用lockfree思想,即Java cas的方法来实现。

  • CASStack
import sun.misc.Unsafe;
import java.lang.reflect.Field;public class CASStack<T>
{private static class Node<TNode>{public Node<TNode> next;public TNode item;@Overridepublic String toString(){return item.toString();}}private Node<T> head;public CASStack(){head = new Node<T>();}/*** 头插法** @param item*/public void push(T item){Node<T> node = new Node<T>();node.item = item;int count = 0;Node<T> oldNextNode;do {oldNextNode = head.next;node.next = oldNextNode;count++;if(count > 1){System.out.println(Thread.currentThread().getName() + " repeated CAS push");}}while (!U.compareAndSwapObject(head, NEXT_OFFSET, oldNextNode, node));}/*** 删除栈顶元素并返回该元素* 没有元素是返回null** @return*/public  T pop(){Node<T> node;int count = 0;do{node = head.next;if (node == null)return null;count++;if(count > 1){System.out.println(Thread.currentThread().getName() + " repeated CAS pop");}}while (!U.compareAndSwapObject(head, NEXT_OFFSET, node, node.next));return node.item;}private static Unsafe U;private static final long NEXT_OFFSET;static {try {Field f ;f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);U=(Unsafe)f.get(null);Class<?> nodeClass = Node.class;NEXT_OFFSET = U.objectFieldOffset(nodeClass.getDeclaredField("next"));}catch (Exception e) {throw new Error(e);}}}
  • 测试代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** Created by chengc on 2018/12/10.*/
public class CASStackTest
{private static final Logger logger = LoggerFactory.getLogger(CASStackTest.class);public static void main(String[] args){CASStack<Integer> stack = new CASStack<Integer>();BlockingQueue putQueue = new LinkedBlockingDeque<>();ExecutorService putThreadPool = new ThreadPoolExecutor(5,10,1, TimeUnit.MINUTES, putQueue, new ThreadPoolExecutor.DiscardPolicy());CountDownLatch pushCountDownLatch = new CountDownLatch(10);AtomicInteger pushCount = new AtomicInteger();for(int i = 0 ; i < 10 ; i++) {final int finalI = i;putThreadPool.submit(() -> {for (int j = 1; j <= 100; j++) {stack.push(j + finalI*100);pushCount.incrementAndGet();}pushCountDownLatch.countDown();});}try {pushCountDownLatch.await();}catch (InterruptedException e) {e.printStackTrace();}logger.info("push done, pushCount={}" , pushCount);putThreadPool.shutdown();boolean[] poppedItems = new boolean[1001];BlockingQueue popQueue = new LinkedBlockingDeque<>();ExecutorService popThreadPool = new ThreadPoolExecutor(5,10,1, TimeUnit.MINUTES, popQueue, new ThreadPoolExecutor.DiscardPolicy());AtomicInteger popCount = new AtomicInteger();CountDownLatch popCountDownLatch = new CountDownLatch(10);for(int i = 0 ; i < 10 ; i++){popThreadPool.submit(() -> {for (int j = 0; j < 100; j++) {int item = stack.pop();if (poppedItems[item] == true) {logger.error("Thread {}: Item {} was popped before!", Thread.currentThread().getName(), item);}else {poppedItems[item] = true;}popCount.incrementAndGet();}popCountDownLatch.countDown();});}try {popCountDownLatch.await();}catch (InterruptedException e) {e.printStackTrace();}popThreadPool.shutdown();logger.info("Pop done, popCount={}" , popCount);}
}
  • 运行结果
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-5 repeated CAS push
pool-1-thread-5 repeated CAS push
pool-1-thread-5 repeated CAS push
pool-1-thread-5 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-1 repeated CAS push
pool-1-thread-5 repeated CAS push
pool-1-thread-5 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-4 repeated CAS push
pool-1-thread-3 repeated CAS push
pool-1-thread-3 repeated CAS push
pool-1-thread-3 repeated CAS push
pool-1-thread-3 repeated CAS push
2018-12-10 13:29:10.164 INFO  [main] demos.lockfree.casstackdemo.CASStackTest,46 - push done, pushCount=1000
pool-2-thread-2 repeated CAS pop
pool-2-thread-4 repeated CAS pop
pool-2-thread-4 repeated CAS pop
pool-2-thread-3 repeated CAS pop
pool-2-thread-3 repeated CAS pop
pool-2-thread-1 repeated CAS pop
pool-2-thread-1 repeated CAS pop
pool-2-thread-2 repeated CAS pop
pool-2-thread-2 repeated CAS pop
2018-12-10 13:29:10.170 INFO  [main] demos.lockfree.casstackdemo.CASStackTest,80 - Pop done, popCount=1000

可以看到,虽然cas过程中有若干次冲突,但是占比并不大。最终结果正确。

0x03 LockLess

LockLess,即无锁编程,是一种用于在不使用锁的情况下安全地操作共享数据的编程思想。有无锁算法可用于传递消息,共享列表和数据队列以及其他任务。

无锁编程非常复杂。 例如所有纯功能性质的数据结构本质上都是无锁的,因为它们是不可变的。

0x04 WaitFree

WaitFree即无等待,他是一个比LockFree更强的场景。

如果一个程序被设计为WaitFree的,这意味着无论线程执行的时间和顺序如何,每个线程都可以保证在任意时间段内都能取得一定进展。

所以我们,在WaitFree场景中,可以说线程都是独立运行的。

注意,所有WaitFree的程序都是LockFree的。

java.util.concurrent.ConcurrentLinkedQueue是WaitFree思想实现的一个例子。

0x05 更多资料

Lock-Free Programming

0xFF 参考文档

What’s the difference between lockless and lockfree?

Examples/Illustration of Wait-free And Lock-free Algorithms

Non-blocking algorithm

Lock-Free 编程

lock free的理解

An Introduction to Lock-Free Programming

LockFree思想相关推荐

  1. 下篇 | 说说无锁(Lock-Free)编程那些事(下)

    6 内存屏障(Memory Barriers) 6.1 What Memory Barriers? 内存屏障,也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的 ...

  2. 算法设计思想(5)— 递归法

    1. 递归概念 递归 Recursion是指在函数的定义中使用函数自身的方法,直观上来看,就是某个函数自己调用自己. ​ 递归有两层含义: 递归问题必须可以分解为若干个规模较小.与原问题形式相同的子问 ...

  3. 卷积神经网络之卷积计算、作用与思想 深度学习

    博客:blog.shinelee.me | 博客园 | CSDN 卷积运算与相关运算 在计算机视觉领域,卷积核.滤波器通常为较小尺寸的矩阵,比如3×33×3.从这个角度看,多层卷积是在进行逐层映射,整 ...

  4. 分治算法的设计思想(二分检索、二分归并排序)

    分治策略思想: 将原问题划分或者归结为规模较小的子问题. 递归或迭代求解每一个问题. 将子问题的解综合得到原问题的解. 性质: 子问题与原问题具有相同的性质. 子问题的求解彼此独立. 划分时子问题的规 ...

  5. AI框架精要:设计思想

    AI框架精要:设计思想 本文主要介绍飞桨paddle平台的底层设计思想,可以帮助用户理解飞桨paddle框架的运作过程,以便于在实际业务需求中,更好的完成模型代码编写与调试及飞桨paddle框架的二次 ...

  6. 算法设计思想(3)— 迭代法

    1. 迭代法基本思想 迭代法的实现,一般需要确定以下三个要点. 1.1 确定迭代变量: 迭代变量一般就是要求解的问题的解,利用迭代递推公式可以不断地由旧值递推出新值.根据问题的不同,迭代变量可以是一个 ...

  7. 算法设计思想(2)— 贪婪法

    1. 贪婪法定义 贪婪法,又称贪心算法,是寻找最优解问题的常用方法,这种方法模式一般将求解过程分成若干个步骤,但每个步骤都应用贪心原则,选取当前状态下最好的或最优的选择(局部最有利的选择),并以此希望 ...

  8. uscao 线段树成段更新操作及Lazy思想(POJ3468解题报告)

    线段树成段更新操作及Lazy思想(POJ3468解题报告) 标签: treequerybuildn2cstruct 2011-11-03 20:37 5756人阅读 评论(0) 收藏 举报  分类: ...

  9. java排序的例子_Java的8大排序的基本思想及实例解读

    本文主要详解了Java语言的8大排序的基本思想以及实例解读,详细请看下文: 8种排序之间的关系: 1, 直接插入排序 (1)基本思想:在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经 ...

最新文章

  1. (volatile int)(x)与*(volatile int *)(x)
  2. iOS 利用JSPatch 添加热补丁功能
  3. Laravel5.2之Filesystem-从Dropbox中下载文件到AWS S3
  4. 关于 varchar2 的最大长度
  5. 神奇的python(四)之logging日志文件系统
  6. win10卸载电脑管家就蓝屏_新电脑WIN10出现蓝屏 系统重装也不行
  7. 最早会外语的人,是怎么回事
  8. 高德地图偏移android,关于JS接高德地图API,以及坐标偏移坐标转换(示例代码)
  9. Windows下Nginx源码编译指南
  10. ssa/ass字幕格式全解析
  11. (2020全新)UGNX二次开发(零基础入门)到提高全套视频教程_NXopen-UG二次开发_新浪博客
  12. 新广告法违规词敏感词检测软件淘宝违规词检测查询工具软件
  13. NPV、IRR的计算
  14. corutine rust_rust 异步 IO:从 mio 到 coroutine
  15. uni-app实战之社区交友APP(12)文章和话题API开发
  16. 准备进入ReRAM速度!Crossbar发布SMIC芯片样品
  17. 【react】This synthetic event is reused for performance reasons.
  18. CNN Architecture
  19. Vollendet und in alle Einzelheiten durchgebildet
  20. 文档布局分析工具之DIVA

热门文章

  1. C++ Invalid read
  2. Excel数据 SQL SERVER
  3. 【SWAT水文模型】SWAT水文模型建立及应用第二期:土地利用数据的准备
  4. Python【小游戏合集】之自己做一个简单又好玩的推箱子小游戏
  5. 手机连接蓝牙通过Zigbee无线控制灯珠亮度
  6. 【面试常问】Java中String和StringBuffer、StringBuilder的区别是什么?
  7. witter被Instagram超越,在中国超越微博的将是谁
  8. 淘宝运营 怎样找到竞争对手 怎样分析竞品
  9. 基于MATLAB的车道线识别、自动驾驶识别
  10. sap委外采购订单冲销 102_ERP软件教程:金蝶ERP委外业务系统的操作(二)