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

Java 使用线程更新易失性布尔数组,java,arrays,multithreading,concurrency,Java,Arrays,Multithreading,Concurrency,我是一名计算机科学专业的学生,目前正在学习并发编程,所以我对线程的知识仍然是,呃,初步的 我只是对用线程更新共享数组的逻辑有点困惑。我正在创建一个程序,它允许潜在的无限多个线程不断更新一个大小为10的布尔数组,以模拟一个座位区的想法,人们可以进去,坐一段随机的时间,然后离开。这是我的密码: class Viewer extends Thread{ private String name; private int index; volatile boolean[] seat

我是一名计算机科学专业的学生,目前正在学习并发编程,所以我对线程的知识仍然是,呃,初步的

我只是对用线程更新共享数组的逻辑有点困惑。我正在创建一个程序,它允许潜在的无限多个线程不断更新一个大小为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;
    }
类查看器扩展线程{
私有字符串名称;
私有整数索引;
[]个席位;
查看器(布尔值[]st,字符串n){
座位=st;
name=n;
}
公开募捐{
观景台vs=新观景台(座椅);
this.index=vs.findSeat(姓名、座位);
试一试{
sleep((long)(Math.random()*1000));
}捕捉(中断异常e){
//TODO自动生成的捕捉块
e、 printStackTrace();
}
席位=vs.leaveSeat(名称、席位、索引);
}
}
类视图标准{
private volatile boolean[]区域;//线程使用的数组
private int seatNo;//获取或留下的任何区域的索引。
Random rand=新的Random();
布尔值=false;
公共视图标准(布尔值st[]){
该面积=st;
}
public int findSeat(字符串s,布尔[]座位){
这个区域=座位;
while(find==false){
对于(int i=0;i

这个程序的结果是数组最初被10个元素填充(我从主程序传递的数组的大小),这些线程然后会留下“an”数组,但显然不是我在两个viewinstand方法之间来回传递的同一个数组,因为在第10个之后的每个后续线程都会被困在寻找座位的过程中。希望有一些输入能为我指明正确的方向。谢谢!

我将首先忽略并发性问题,然后直接查看所看到的内容ms就像您询问的逻辑错误一样-
leaveSeat
正在设置
此。区域[n]=false
-这似乎表明座位已被占用(如果值为
true
,则您的
findSeat
方法假定座位为空)


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

在并发端

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

  • 您对数组的一个元素执行单独的读写操作。Volatile意味着一次读取可以保证看到即时正确的值,即任何线程最后一次写入的值。但它不会阻止争用条件

    在您的代码中,一个线程执行一次读取测试,以确定一个席位是否空闲,然后执行一次写入以保留该席位。该序列不是原子序列。没有任何东西可以阻止另一个线程在该线程的读取和写入之间“抓住席位”

  • 不幸的是,确保您的代码不存在此类问题的唯一方法是执行形式化分析(即构造一个数学上可靠的证明)从Java内存模型1的指定语义开始。这很困难。因此,通常建议使用
    Java.util.concurrent
    Java.util.concurrent.atomic
    Java.util.concurrent.locks
    包提供的标准构建块



    1-如果您了解JMM,可以首先对代码样式进行非正式分析。允许查看者对数组进行原始访问。这违反了OO设计的原则。布尔数组应该由
    查看标准
    封装,仅由其方法处理

    重写后,代码看起来会更好,但由于并发性问题,仍然是错误的


    对实际数据的访问,
    boolean[]
    的内容不是易变的。使用关键字的方式只会使对数据的引用变为易变的。由于引用根本没有改变,添加的
    volatile
    除了可能使访问变慢之外,没有任何作用

    即使您成功地对数组的内容进行了易失性读写,仍然存在并发性问题,因为检查空闲座位并占用它不是原子的

    使访问原子化的一种方法是添加锁(使方法
    同步化
    ),从本质上说,强制对
    ViewingStand
    的访问一个接一个地进行。通过锁强制执行的“发生在之前”顺序,您甚至不需要
    volatile

    但是,如果将锁添加到
    findSeat
    ,则第n+1个
    查看器将保持锁,并继续查找空的