大家好,又见面了,我是你们的朋友全栈君。
近期看一些源码,会有一些注释是LockFree
。这到底啥玩意儿?之前我也不知道啊,遂赶紧上网查之,总结了一些东西作为记录,与大家分享。
先上一张神图:
由上图可以看出,LockFree程序必须满足三个条件:
具体来说,如果一个程序是LockFree的,则在运行足够长的一段时间内至少一个线程能取得进展。如果一个程序死锁,这个程序内的所有线程都没法取得进展了,这种情况就不能成为LockFree。在LockFree的程序中,如果一个线程被挂起,决不能影响其他线程继续执行,也就是说是非阻塞的。
因为LockFree能保证有一个线程能执行并取得进展,所以在代码不是无限循环的情况下可以保证所有线程最终能完成自己的工作。
java.util.concurrent.atomic
包是LockFree思想实现的例子。
LockFree编程的特点:
原子操作不可再分割。
RMW的设计可让执行更复杂的事务操作变成原子的,使得多个写入者尝试对相同内存区域进行修改时,保证一次只能执行一个操作。
在x86/64的CPU架构中,通过CAS方式实现。其他架构各有不同实现方式。
上面提到了CAS,那么就会有ABA问题。 详细内容可以看我之前写的一篇文章:Java-并发-CAS中的ABA章节部分。
顺序一致性内存模型有两大特性
以Stack为例子,实现一个LockFree的Stack。
这个例子中使用单链表实现栈,有两个方法:
public class SimpleStack<T>
{
private class Node<TNode>
{
public Node<TNode> next;
public TNode item;
@Override
public 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多次的情况!
在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
但是,以上采用的同步锁方式在高并发场景下,大量出现线程竞争的情况,效率并不高。
下面采用lockfree思想,即Java cas的方法来实现。
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;
@Override
public 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过程中有若干次冲突,但是占比并不大。最终结果正确。
LockLess
,即无锁编程,是一种用于在不使用锁的情况下安全地操作共享数据的编程思想。有无锁算法可用于传递消息,共享列表和数据队列以及其他任务。
无锁编程非常复杂。 例如所有纯功能性质的数据结构本质上都是无锁的,因为它们是不可变的。
WaitFree
即无等待,他是一个比LockFree更强的场景。
如果一个程序被设计为WaitFree的,这意味着无论线程执行的时间和顺序如何,每个线程都可以保证在任意时间段内都能取得一定进展。
所以我们,在WaitFree场景中,可以说线程都是独立运行的。
注意,所有WaitFree的程序都是LockFree的。
java.util.concurrent.ConcurrentLinkedQueue
是WaitFree思想实现的一个例子。
What’s the difference between lockless and lockfree?
Examples/Illustration of Wait-free And Lock-free Algorithms
An Introduction to Lock-Free Programming
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/161675.html原文链接:https://javaforall.cn