线程执行顺序Java



PhysicsThread.java启动一个新线程。run方法执行initialize方法。这种方法创造了物理世界。之后,它开始一个while循环,以1000/60=16毫秒的间隔更新世界。然而,我在循环中得到了一个nullpointerexception,因为它并不总是知道世界,即使我检查了它能解释发生了什么吗

PhysicsThread.java

public class PhysicsThread implements Runnable {
long fps = 60;
DiscreteDynamicsWorld dw;
long lasttime = System.currentTimeMillis();;
static int maxPhysicsObjects = 50000;
PhysicsThread() {
    Thread t = new Thread(this);
    t.setPriority(Thread.MIN_PRIORITY);
    t.start();
}
@Override
public void run() {
    System.out.println("Start thread");
    init();
    System.out.println("Start threadloop");
    while(true) {
        loop();
    }
}
public void init() {
    BroadphaseInterface broadphase = new AxisSweep3_32(new Vector3f(0,0,0), new Vector3f(200000,200000,200000), maxPhysicsObjects);//new DbvtBroadphase();
    DefaultCollisionConfiguration collisionConfiguration = new DefaultCollisionConfiguration();
    CollisionDispatcher dispatcher = new CollisionDispatcher(
            collisionConfiguration);
    SequentialImpulseConstraintSolver solver = new SequentialImpulseConstraintSolver();
    dw = new DiscreteDynamicsWorld(dispatcher, broadphase,
            solver, collisionConfiguration);
    System.out.println("Made world");
    dw.setGravity(new Vector3f(0, 0, -10));
}
public void loop() {
    if (dw!=null) {
        float delta = System.currentTimeMillis()-lasttime;
        lasttime = System.currentTimeMillis();
        dw.stepSimulation(delta/1000f, 1, 1/60f);
    }
    try {
        Thread.sleep(1000/fps);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
public DiscreteDynamicsWorld getWorld() {
    return dw;
}

}

我怀疑这可能是由于您的构建技术,这是一种反模式。在构造函数期间,不应该让this引用"转义",因为这样其他线程可能会看到处于部分构造状态的对象。在这一点上,关于类的状态的所有赌注都是否定的,即使是始终为真的不变量(例如最终字段被设置为值)也可能不成立。

我想知道这里发生的事情是否是由于以下事件序列:

  • 调用构造函数并基于this启动新线程
  • 新线程运行,并在被抢占之前完全执行init(设置dw)和loop的一半
  • 主线程继续使用构造函数,并将dw设置为null,作为字段初始化的一部分
  • 派生线程在计算出deltalastTime之后继续,然后看到dw的空值

我有点犹豫地说,这正是的情况,因为我本来希望在构造函数体运行之前初始化字段。然而,在对象仍在构建时访问字段似乎是一个非常糟糕的主意。

为了解决这个问题,我建议不要在构造函数中启动线程,而是让调用代码在之后启动线程,例如:

final PhysicsThread pt = new PhysicsThread();
final Thread t = new Thread(pt);
t.setPriority(Thread.MIN_PRIORITY);
t.start();

(此外,让init方法不是构造函数的想法通常是个坏主意-如果dw字段永远不会更改,为什么不在构造函数本身执行init的所有工作,并将dw标记为final?这会更干净,也会让代码更容易推理,因为VM保证在构造过程中完全设置该值,并且它永远不会更改e之后。唯一的缺点是,如果你构建了很多这样的实例,但从未真正运行过它们,那么性能就会受到影响——所以不要这样做。:)

另外,类名具有误导性,因为它不是Thread,而是Runnable。可以说,将其称为PhysicsTaskPhysicsSimulationRun会更清楚。)