浅谈Java中ABA问题及避免

 更新时间:2018年01月16日 11:02:45   作者:li954644351  
这篇文章主要介绍了浅谈Java中ABA问题及避免,具有一定借鉴价值,需要的朋友可以参考下

本文主要研究的是关于Java中ABA问题及避免的相关内容,具体如下。

在《Java并发实战》一书的第15章中有一个用原子变量实现的并发栈,代码如下:

public class Node {
	public final String item;
	public Node next;
	public Node(String item){
		this.item = item;
	}
}
public class ConcurrentStack {
	AtomicReference<Node> top = new AtomicReference<Node>();
	public void push(String item){
		Node newTop = new Node(item);
		Node oldTop;
		do{
			oldTop = top.get();
			newTop.next = oldTop;
		}
		while(!top.compareAndSet(oldTop, newTop));
	}
	public String pop(){
		Node newTop;
		Node oldTop;
		do{
			oldTop = top.get();
			if(oldTop == null){
				return null;
			}
			newTop = oldTop.next;
		}
		while(!top.compareAndSet(oldTop, newTop));
		return oldTop.item;
	}
}

这个例子并不会引发ABA问题,至于为什么不会,后面再讲解,下面先讲一下ABA问题

什么是ABA?

引用原书的话:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令就可能出现这种问题,在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作,在某些算法中,如果V的值首先由A变为B,再由B变为A,那么CAS将会操作成功

ABA的例子

有时候,ABA造成的后果很严重,下面将并发栈的例子修改一下,看看ABA会造成什么问题:

public class Node {
	public final String item;
	public Node next;
	public Node(String item){
		this.item = item;
	}
}
public class ConcurrentStack {
	AtomicReference<Node> top = new AtomicReference<Node>();
	public void push(Node node){
		Node oldTop;
		do{
			oldTop = top.get();
			node.next = oldTop;
		}
		while(!top.compareAndSet(oldTop, node));
	}
	public Node pop(int time){
		Node newTop;
		Node oldTop;
		do{
			oldTop = top.get();
			if(oldTop == null){
				return null;
			}
			newTop = oldTop.next;
			TimeUnit.SECONDS.sleep(time);
		}
		while(!top.compareAndSet(oldTop, newTop));
		return oldTop;
	}
}

注意这里的变化,Node基本没有变化

重点关注ConcurrentStack的变化

1、push方法:原来是使用内容构造Node,现在直接传入Node,这样就符合了“在算法中的节点可以被循环使用”这个要求

2、pop方法的sleep,这是模拟线程的执行情况,以便观察结果

我们先往stack中压入两个Node:

ConcurrentStack stack = new ConcurrentStack(); 
stack.push(new Node("A")); 
stack.push(new Node("B")); 

然后创建两个线程来执行出入栈的操作

线程A先执行出栈:让NodeA出栈

stack.pop(3); 

因为某些原因,线程A执行出栈比较久,用了3s

线程B执行出栈之后再入栈:先然NodeA和NodeB出栈,然后让NodeD,NodeC,NodeA入栈(NodeA在栈顶)

Node A = stack.pop(0); 
stack.pop(0); 
stack.push(new Node("D")); 
stack.push(new Node("C")); 
stack.push(A); 

注意:线程B实现了节点的循环利用,它先将栈里面的内容全部出栈,然后入栈,最后栈顶的内容是之前出栈的Node

线程B执行完这些动作之后,线程A才执行CAS,此时CAS是可以执行成功的

按照原来的想法,线程A和B执行之后,stack的内容应该是:C和D,C在栈顶,但这里的执行结果却是Stack中什么都没有,这就是ABA问题

如何避免ABA问题

Java中提供了AtomicStampedReference和AtomicMarkableReference来解决ABA问题

AtomicStampedReference可以原子更新两个值:引用和版本号,通过版本号来区别节点的循环使用,下面看AtomicStampedReference的例子:

public class ConcurrentStack {
	AtomicStampedReference<Node> top = new AtomicStampedReference<Node>(null,0);
	public void push(Node node){
		Node oldTop;
		int v;
		do{
			v=top.getStamp();
			oldTop = top.getReference();
			node.next = oldTop;
		}
		while(!top.compareAndSet(oldTop, node,v,v+1));
		//   }while(!top.compareAndSet(oldTop, node,top.getStamp(),top.getStamp()+1));
	}
	public Node pop(int time){
		Node newTop;
		Node oldTop;
		int v;
		do{
			v=top.getStamp();
			oldTop = top.getReference();
			if(oldTop == null){
				return null;
			}
			newTop = oldTop.next;
			try {
				TimeUnit.SECONDS.sleep(time);
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		while(!top.compareAndSet(oldTop, newTop,v,v+1));
		//   }while(!top.compareAndSet(oldTop, newTop,top.getStamp(),top.getStamp())); 
		return oldTop;
	}
	public void get(){
		Node node = top.getReference();
		while(node!=null){
			System.out.println(node.getItem());
			node = node.getNode();
		}
	}
}

注意:不能使用注释中的方式,否则就和单纯使用原子变量没有区别了

AtomicMarkableReference可以原子更新一个布尔类型的标记位和引用类型,看下面的例子:

AtomicMarkableReference<Node> top = new AtomicMarkableReference<Node>(null,true);
public void push(Node node){
	Node oldTop;
	Boolean v;
	do{
		v=top.isMarked();
		oldTop = top.getReference();
		node.next = oldTop;
	}
	while(!top.compareAndSet(oldTop, node,v,!v));
}

总结

以上就是本文关于浅谈Java中ABA问题及避免的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

相关文章

  • JavaSE、JavaEE和JavaWeb三大工程目录详解

    JavaSE、JavaEE和JavaWeb三大工程目录详解

    这篇文章主要给大家介绍了关于JavaSE、JavaEE和JavaWeb三大工程目录的相关资料,很多对java不是很了解的同学在看到课程⼤纲的时候发现⾥⾯出现了JavaSE、JavaEE、JavaME、JavaWEB这些词,搞得⼀头雾⽔,需要的朋友可以参考下
    2023-07-07
  • Java Apache Shiro安全框架快速开发详解流程

    Java Apache Shiro安全框架快速开发详解流程

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序
    2021-10-10
  • Java通过jersey实现客户端图片上传示例

    Java通过jersey实现客户端图片上传示例

    本篇文章主要介绍了Java通过jersey实现客户端图片上传示例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • Spring IOC 三种配置方式详解

    Spring IOC 三种配置方式详解

    这篇文章主要介绍了Spring IOC 三种配置方式,基于xml配置方式组件管理,基于注解方式管理和配置类方式管理,这三种方式,通过图文讲解的非常详细,需要的朋友可以参考下
    2024-05-05
  • 详解在springboot中使用Mybatis Generator的两种方式

    详解在springboot中使用Mybatis Generator的两种方式

    这篇文章主要介绍了详解在springboot中使用Mybatis Generator的两种方式,本文将介绍到在springboot的项目中如何去配置和使用MBG以及MBG生成代码的两种方式,非常具有实用价值,需要的朋友可以参考下
    2018-11-11
  • Java线程池运行状态监控实现解析

    Java线程池运行状态监控实现解析

    这篇文章主要介绍了Java线程池运行状态监控实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • java基于数据库实现全局唯一ID的示例

    java基于数据库实现全局唯一ID的示例

    本文主要介绍了java基于数据库实现全局唯一ID的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • 一篇文章带你认识Java8接口的默认方法

    一篇文章带你认识Java8接口的默认方法

    这篇文章主要给大家介绍了如何通过一篇文章带你认识Java8接口的默认方法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java8具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05
  • java对象转换String类型的三种方法

    java对象转换String类型的三种方法

    在很多情况下我们都需要将一个对象转换为String类型。一般来说有三种方法可以实现:Object.toString()、(String)Object、String.valueOf(Object)。下面对这三种方法一一分析
    2013-11-11
  • PowerJob的QueryConvertUtils工作流程源码解读

    PowerJob的QueryConvertUtils工作流程源码解读

    这篇文章主要为大家介绍了PowerJob的QueryConvertUtils工作流程源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01

最新评论