我试图在java中使用线程时结合使用list.add
和list.remove
来解决一个问题。假设我们和Stack一起玩
这是我的Stack定义类。
import java.util.ArrayList;
public class Stack {
private int size;
private int maxSize;
private final ArrayList<Object> list;
public Stack(int size) {
this.size = 0;
this.maxSize = size;
this.list = new ArrayList<Object>(size);
}
public boolean push(Object o) {
if (size >= maxSize) {
return false;
}
this.list.add(0, o);
this.size++;
return true;
}
public Object pop() {
Object o;
if (this.size == 0) {
return null;
}
o = this.list.remove(0);
this.size--;
return o;
}
public int size() {
return this.size;
}
}
下面是我们如何在Java线程中使用堆栈
final Stack stack = new Stack(4);
for(int i = 0; i < 10000; i++) {
final String data = "hello " + i;
final int x = i;
new Thread(new Runnable() {
public void run() {
if(x % 2 == 0) {
System.out.println(stack.push(data));
} else {
System.out.println(stack.pop());
}
}
}).start();
}
基本上,我们只需创建10000个线程来操作Stack对象。stack.push
导致True (如果堆栈尚未满)或false (如果堆栈已经满),如果堆栈为空,则stack.pop
结果为null
问题是:上面的Stack实现有什么问题,以及如何修复它?
到目前为止,我的分析是线程如何在java中运行。线程以并行方式运行,而不是按顺序运行。我试着执行这个程序,有时会出现异常IndexOutOfBounds
。如果我的分析是正确的(或接近),有什么办法可以避免例外吗?也许在Stack类中包括一些检查方法?
如果我的分析是错误的,那么上面的实现有什么问题呢?以及如何修复它?
发布于 2015-08-04 09:07:16
上面的堆栈实现有什么问题?
当只有一个线程可以访问堆栈对象时,您的实现是好的。但是,如果至少有2个线程可以在同一个堆栈上执行pop和push操作-- https://stackoverflow.com/questions/34510/what-is-a-race-condition就会发生。JSR-133描述了java多线程的主要描述。
想象一下使用pop
方法编写的代码的情况:
if (this.size == 0) {
return null;
}
o = this.list.remove(0);
你需要确保有些事情发生在其他事件之前。其中一种方法是同步pop
和push
方法。这可以通过在方法返回类型之前添加synchronized
关键字来实现。
public synchronized boolean push(Object o) {...}
public synchronized Object pop() { ...}
这里,这两种方法都是同一个对象的synchronized
-- this
。因此,当一个线程通过执行this
或push
获得pop
锁时,没有其他线程能够进入由同一个this
对象锁定(同步)的代码块或方法。使用这些方法是完全线程安全的。
如果您使用一些同步集合而不是常规的ArrayList
,您仍然会遇到麻烦,因为您的逻辑依赖于size
变量,而前面的错误场景仍然有效。如果需要并发Stack
实现,可以使用java.util.concurrent
包中的LinkedBlockingDeque
类。它还会更高效,因为向ArrayList
的开头添加一个元素的成本非常高。但是,如果您想单独启用它,只需向pop
和push
方法添加同步修饰符,并将列表更改为LinkedList
。
发布于 2015-08-04 11:15:37
@ka4eli告诉您为什么Stack类不是线程安全的,但这也是错误的:
if(x % 2 == 0) {
System.out.println(stack.push(data));
} else {
System.out.println(stack.pop());
}
即使您的堆栈是完全线程安全的,在其他情况下也一定会得到NullPointerExceptions。
不同步线程可以按任何顺序运行。仅仅因为您的程序在启动线程1之前启动线程0,并不意味着线程1在线程0(或任何其他线程)之前不会尝试从堆栈中弹出字符串。
https://stackoverflow.com/questions/31815217
复制