简体   繁体   English

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

[英]Updating volatile boolean array using Threads

I'm a CS student currently learning about concurrent programming so my knowledge on threads is still, uh, tentative. 我是一名CS学生,目前正在学习并发编程,所以我对线程的知识仍然是,暂时的。

I'm just a bit stuck at the logic of updating a shared array with threads. 我只是停留在用线程更新共享数组的逻辑上。 I'm creating a program that allows a potentially infinite number of threads to constantly update a boolean array of size 10 to simulate the idea of a seating area where people can go in, sit down for a random amount of time, and then leave. 我正在创建一个程序,该程序允许潜在的无限数量的线程不断更新大小为10的布尔数组,以模拟人们可以进入,坐下一段随机时间然后离开的休息区的想法。 Here is my code: 这是我的代码:

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;
    }

The result of this program is the array initially getting filled with 10 elements (the size of the array I passed from the main program), those threads then leave 'an' array but clearly not the same one I'm passing back and forth between both ViewingStand methods, as every subsequent thread after the 10th gets stuck looking for a seat. 该程序的结果是,数组最初填充了10个元素(我从主程序传递的数组的大小),然后这些线程离开了“一个”数组,但显然我在两个数组之间来回传递的数组不相同这两个ViewingStand方法,因为第十个之后的每个后续线程都会卡住以寻找座位。 Would love some input to point me in the right direction. 很乐意为我提供正确的指导。 Thank you! 谢谢!

I'll ignore the concurrency issues at first and go straight for what seems like the logic error you're asking about - leaveSeat is setting this.area[n] = false - which seems to indicate that the seat is taken (your findSeat method assumes a seat is empty if the value is true ). 首先,我将忽略并发性问题,直接查找您要询问的逻辑错误leaveSeat设置了this.area[n] = false这似乎表明该座位已被占用(您的findSeat方法如果值为true则假定座位为空)。

On concurrency issues: You're likely to have issues with your loop checking the seats - it's possible for multiple threads to determine a seat is empty (and go into the if block), and all "claim" the same seat. 关于并发性问题:循环检查席位时可能会遇到问题-多个线程可能会确定席位为空(并进入if块),并且所有“声明”同一个席位。 You should construct one instance of ViewingStand and have it manage access to the seats - using concurrency controls like synchronized or locking to ensure multiple threads don't modify the state of the seats at the same time. 您应该构造一个ViewingStand实例,并让它管理对座位的访问-使用并发控件(例如synchronized或锁定)来确保多个线程不会同时修改座位的状态。

On the concurrency side ... 在并发方面...

  1. A volatile boolean[] is unlikely to be thread-safe. volatile boolean[]不太可能是线程安全的。 The volatile semantics apply to the array reference only, not to the access and updates to the elements of the array. volatile语义仅适用于数组引用,而不适用于对数组元素的访问和更新。

  2. You performing a separate read and write on an element of the array. 您对数组的元素执行单独的读取和写入。 Volatile means that a single read is guaranteed to see the instantaneously correct value; 易失性意味着可以保证单次读取可以看到瞬时正确值; ie the value of the last write from any thread. 即来自任何线程的最后一次写入的值。 But it doesn't prevent race conditions. 但这并不能阻止比赛条件。

    Your code, a thread performs a read to test if a seat is free followed by a write to reserve it. 您的代码将由线程执行读取操作,以测试座位是否空闲,然后写入以保留该座位。 That sequence is not atomic. 该序列不是原子的。 Nothing prevents another thread from "grabbing the seat" in between this thread's read and write. 没有什么可以阻止另一个线程在该线程的读写之间“抢夺座位”。

Unfortunately, the only way to guarantee that your code doesn't have this kind of problem is to perform a formal analysis (ie construct a mathematically sound proof) starting from the specified semantics of the Java Memory Model 1 . 不幸的是,确保您的代码不存在此类问题的唯一方法是从Java内存模型1的指定语义开始执行形式化分析 (即,构造数学上可靠的证明)。 This is difficult. 这是困难的。 Hence, the normal recommendation is to use the standard building blocks provided by the java.util.concurrent , java.util.concurrent.atomic and java.util.concurrent.locks packages. 因此,通常的建议是使用java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks包提供的标准构建块。


1 - If you understand the JMM, an informal analysis may be acceptable ... 1-如果您了解JMM,则可以接受非正式分析...

On the code style first. 首先谈代码风格。 The Viewer s are given raw access to the array. Viewer被赋予对数组的原始访问权限。 This goes against the philosophy of OO design. 这违背了面向对象设计的哲学。 The boolean array should be encapsulated by the ViewingStand , handled only by the methods of it. 布尔数组应该由ViewingStand封装,只能由其方法处理。

After the rewrite the code will look better, but still be wrong because of concurrency issues. 重写后,代码看起来会更好,但是由于并发问题仍然是错误的。


The access to the actual data, the content of the boolean[] , is not volatile. 对实际数据(即boolean[]的内容)的访问不是不稳定的。 The way you use the keyword only make the reference to the data volatile. 您使用关键字的方式只会使对数据的引用易变。 As the reference is not changed at all, the added volatile does not do anything except maybe make the access slower. 由于引用完全没有更改,因此添加的volatile不会执行任何操作,只会使访问速度变慢。

Even if you managed to have volatile reads and writes to the content of the array, there is still concurrency issues as the checking for a free seat and taking it is not atomic. 即使您成功地对数组的内容进行了易失性的读写,但仍然存在并发性问题,因为检查自由席位并获取它不是原子的。

A way to make the access atomic is to add locks (make the methods synchronized ), essentially forcing the access to the ViewingStand to happen one by one. 使访问原子化的一种方法是添加锁(使方法synchronized ),从本质上迫使对ViewingStand的访问一个接一个地进行。 With the "happens-before" ordering enforced by the lock, you do not even need volatile . 借助锁强制执行的“ happens-before”排序,您甚至不需要volatile

However if you add the lock to findSeat , the n+1 th Viewer will hold the lock, keep looking for an empty seat, while the first n Viewer s wait for the lock so that they can run leaveSeat . 但是,如果将锁添加到findSeat ,第n + 1 Viewer将持有该锁,继续寻找空座位,而前n个Viewer等待该锁,以便他们可以运行leaveSeat A deadlock. 僵局。 The synchronized keyword should be added to a method that loops through the array once but not the ever looping findSeat method. synchronized关键字应该被添加到通过阵列循环一次而不是不断循环的方法findSeat方法。


Another way, and a powerful idea that you can apply even in database accesses, is Compare-and-swap . 另一种方法,甚至可以在数据库访问中应用的强大思想是Compare-and-swap This is one instruction that changes the data only if the prior value is what you expected. 这是一条仅在先前值是您期望的值时才更改数据的指令。 Instead of boolArray[i] = true , you can use an array of AtomicBoolean s and do atomicBoolArray[i].compareAndSet(false, true) . 代替boolArray[i] = true ,您可以使用AtomicBoolean数组并做atomicBoolArray[i].compareAndSet(false, true) If compareAndSet returns false, that means another Viewer has got the seat earlier. 如果compareAndSet返回false,则表示另一个Viewer已获得更早的位置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM