使用线程更新易失布尔数组

时间:2018-11-23 00:16:17

标签: java arrays multithreading concurrency

我是一名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方法之间来回移动,因为第十个之后的每个后续线程都会卡住以寻找座位。很乐意为我提供正确的指导。谢谢!

3 个答案:

答案 0 :(得分:3)

首先,我将忽略并发问题,直奔您所要询问的逻辑错误-leaveSeat设置this.area[n] = false-似乎表明席位已坐(如果值为findSeat,则您的true方法假定座位为空)。

关于并发问题:循环检查席位时可能会遇到问题-多个线程可能会确定席位为空(并进入if块),并且所有“声明”同一个席位。您应该构造ViewingStand的一个实例,并使其管理对席位的访问-使用synchronized之类的并发控件或锁定以确保多个线程不会同时修改席位的状态。 / p>

答案 1 :(得分:2)

在并发方面...

  1. volatile boolean[]不太可能是线程安全的。 volatile语义仅适用于数组引用,而不适用于对数组元素的访问和更新。

  2. 您将对数组的元素执行单独的读取和写入操作。易失性意味着可以保证单次读取可以看到瞬时正确值;即来自任何线程的最后一次写入的值。但这并不能阻止比赛条件。

    您的代码将由线程执行读取操作以测试座位是否空闲,然后执行写入操作以保留该座位。该序列不是原子的。没有什么可以阻止另一个线程在该线程的读写之间“抢夺座位”。

不幸的是,确保您的代码不存在此类问题的唯一方法是从Java的指定语义开始执行形式分析(即构建数学上可靠的证明)内存模型 1 。这是困难的。因此,通常的建议是使用java.util.concurrentjava.util.concurrent.atomicjava.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早就获得了席位。