【问题标题】:Variable is null when called from another thread从另一个线程调用时变量为空
【发布时间】:2015-04-27 12:17:56
【问题描述】:

请向我解释变量的奇怪行为。 从主线程创建类“B”的实例。 父“A”的构造函数被称为“B”类的抽象函数“init”。 它初始化“B”类的 debugPaint 成员。

然后,它创建一个线程,定期调用函数postDraw。 问题是,如果我指定 private volatile Paint debugPaint=null 函数 postDraw 接收 debugPaint 成员作为 null。尽管正如我在调试器中看到的,之前的初始化是成功的。 如果未完成对 null 的分配,则一切正常。 private volatile Paint debugPaint; 有什么问题?

p.s init 和 postDraw 之间的时间很长,只有几秒钟。

public class A{

  public A()
  {
    init();
  }

  public void draw(Canvas canvas)
  {
    //some code....
   postDraw(canvas);
  }

  abstract public void postDraw(Canvas canvas);
  abstract public void init();
}


public class B extends A{

    private volatile Paint  debugPaint=null;//=null problem! not =null ok!

    @Override
    public void init()
    {
        debugPaint=new Paint();
    }


    @Override
    public void postDraw(Canvas canvas)
    {
       canvas.drawRect(0,0,128,128,debugPaint);
    }
}

【问题讨论】:

  • 请发布一个简短但完整的程序来演示该问题。我们这里只有部分代码。 (另外,请遵循示例代码中的 Java 命名约定,以避免认知失调。)
  • 赞成“认知失调”一词
  • 为什么不使用字段初始化,即private final Paint debugPaint = new Paint();?顺便说一句:构造函数永远不应该调用抽象或可覆盖的方法! (见stackoverflow.com/questions/15327417/…
  • @isnot2bad 例如,我已经简化了。其实debugPaint的初始化过程比较复杂。

标签: java multithreading variables initialization


【解决方案1】:

您的问题与线程无关。

下面的例子是一个演示问题的完整程序:

main() 例程创建一个新的 B 实例时,它首先调用 A() 构造函数。该构造函数调用 B.init() 将 debugPaint 设置为指向一个新的 Paint 对象。然后,在 A() 构造函数完成后,default B() 构造函数被调用...

class Paint {
}

class Canvas {
}

abstract class A{

  public A()
  {
    System.out.println("A.<init>() entered");
    init();
    System.out.println("A.<init>() exit");
  }

  public void draw(Canvas canvas)
  {
    //some code....
   postDraw(canvas);
  }

  abstract public void postDraw(Canvas canvas);
  abstract public void init();
}


class B extends A{

    private volatile Paint  debugPaint=null;   //this assignment happens in the default B() constructor

    @Override
    public void init()
    {
        System.out.println("B.init() entered");
        debugPaint=new Paint();
        System.out.println("B.init() exit");
    }


    @Override
    public void postDraw(Canvas canvas)
    {
       System.out.println("debugPaint=" + debugPaint);
    }
}

public class Foobar {
    public static void main(String[] args) {
        B b = new B();
        b.draw(new Canvas());
    }
}

【讨论】:

  • 我完全同意这个答案:这与线程无关。不应在构造函数中调用任何虚方法(如此处的 init())。这里的解决方案就是让B调用init()(这不是虚拟方法,只是一个本地助手)。
  • @AbbéRésina Java 中的所有方法都是“虚拟”方法:即使 A 类声明了具体的 init() 方法,A() 构造函数中的 init() 调用仍然会调用 B.init( )。您从构造函数调用的任何方法都应声明为final
  • @jameslarge 谢谢。 init 被调用,不仅是从构造函数等我通过抽象实现的。删除 =null 是否足以避免这种情况?
  • @jameslarge,同意。我的意思是在A 类中使用abstract init() 并把B 本地助手init() 设置为final 是没有用的。
  • @Mixer no,删除 = null 无济于事,因为这是隐含的。只是不要在构造函数中调用被覆盖的方法!从来没有!
【解决方案2】:

我认为这里的问题是在构造函数中声明了新线程(尽管是间接的),因此在新线程启动时没有完全实例化A。

在 Java 中,不建议从构造函数启动线程。相反,应该在构造之后调用 init()。那应该没问题。

Java: starting a new thread in a constructor

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 1970-01-01
    • 2013-07-19
    • 2021-04-09
    • 2019-07-11
    相关资源
    最近更新 更多