我是一名CS学生,目前正在学习并发编程,所以我对线程的知识仍然是,暂时的。
我只是停留在用线程更新共享数组的逻辑上。我正在创建一个程序,该程序允许潜在的无限数量的线程不断更新大小为10的布尔数组,以模拟人们可以进入,坐下一段随机时间然后离开的休息区的想法。这是我的代码:
class Viewer extends Thread{
private String name;
private int index;
volatile boolean[] seats;
Viewer(boolean[] st, String n){
seats = st;
name = n;
}
public void run() {
ViewingStand vs = new ViewingStand(seats);
this.index = vs.findSeat(name, seats);
try {
Thread.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
seats = vs.leaveSeat(name, seats, index);
}
}
class ViewingStand{
private volatile boolean[] area; //the array used by threads
private int seatNo; //index of whatever area is being taken or left.
Random rand = new Random();
boolean found = false;
public ViewingStand(boolean st[]){
this.area = st;
}
public int findSeat(String s, boolean[] seats){
this.area = seats;
while(found == false) {
for(int i=0; i < area.length; i++) {
if(area[i] == true) {
found = true;
this.seatNo = i;
area[seatNo] = false;
System.out.println(s + " has found a seat.");
return this.seatNo;
}
}
System.out.println(s + " has started searching again.");
}
return -1; //should never reach this
}
public boolean[] leaveSeat(String s, boolean[] area, int n){
this.area = area;
this.area[n] = false;
System.out.println(s + " has left their seat.");
return this.area;
}
该程序的结果是,数组最初填充了10个元素(我从主程序传递的数组的大小),然后这些线程留下了“一个”数组,但显然与我传递的数组不同在两个ViewingStand方法之间来回移动,因为第十个之后的每个后续线程都会卡住以寻找座位。很乐意为我提供正确的指导。谢谢!
答案 0 :(得分:3)
首先,我将忽略并发问题,直奔您所要询问的逻辑错误-leaveSeat
设置this.area[n] = false
-似乎表明席位已坐(如果值为findSeat
,则您的true
方法假定座位为空)。
关于并发问题:循环检查席位时可能会遇到问题-多个线程可能会确定席位为空(并进入if块),并且所有“声明”同一个席位。您应该构造ViewingStand
的一个实例,并使其管理对席位的访问-使用synchronized
之类的并发控件或锁定以确保多个线程不会同时修改席位的状态。 / p>
答案 1 :(得分:2)
在并发方面...
volatile boolean[]
不太可能是线程安全的。 volatile
语义仅适用于数组引用,而不适用于对数组元素的访问和更新。
您将对数组的元素执行单独的读取和写入操作。易失性意味着可以保证单次读取可以看到瞬时正确值;即来自任何线程的最后一次写入的值。但这并不能阻止比赛条件。
您的代码将由线程执行读取操作以测试座位是否空闲,然后执行写入操作以保留该座位。该序列不是原子的。没有什么可以阻止另一个线程在该线程的读写之间“抢夺座位”。
不幸的是,确保您的代码不存在此类问题的唯一方法是从Java的指定语义开始执行形式分析(即构建数学上可靠的证明)内存模型 1 。这是困难的。因此,通常的建议是使用java.util.concurrent
,java.util.concurrent.atomic
和java.util.concurrent.locks
软件包提供的标准构件。
1-如果您了解JMM,则可以接受非正式分析...
答案 2 :(得分:0)
首先在代码样式上。 Viewer
被赋予对数组的原始访问权限。这违背了面向对象设计的哲学。布尔数组应由ViewingStand
封装,只能由其方法处理。
重写后,代码看起来会更好,但由于并发问题仍然是错误的。
对实际数据(boolean[]
的内容)的访问是不稳定的。您使用关键字的方式只会使对数据的引用易变。由于引用完全没有更改,因此添加的volatile
不会执行任何操作,只会使访问速度变慢。
即使您设法对数组的内容进行易失性的读写,仍然存在并发性问题,因为检查自由席位和获取席位不是原子的。
使访问原子化的一种方法是添加锁(使方法synchronized
),本质上是强制对ViewingStand
的访问一个接一个地进行。通过锁强制执行“在发生之前”的顺序,您甚至不需要volatile
。
但是,如果您将锁添加到findSeat
,第n + 1 th Viewer
将持有锁,继续寻找空座位,而前n个{ {1}}个等待锁定,以便它们可以运行Viewer
。僵局。
应该将leaveSeat
关键字添加到一次循环遍历数组的方法中,而不要添加到永远循环的synchronized
方法中。
Compare-and-swap是一种甚至可以在数据库访问中应用的强大方法。
这是一条仅在先前值是您期望的值时才更改数据的指令。可以使用findSeat
的数组代替boolArray[i] = true
来执行AtomicBoolean
。如果atomicBoolArray[i].compareAndSet(false, true)
返回false,则意味着另外一个compareAndSet
早就获得了席位。