在构造函数中泄漏此ImageObserver是否安全?

时间:2014-12-25 20:16:10

标签: java multithreading image swing

我正在处理一个包含Image的类,并以0,0为中心绘制它;为此,它检索图像的高度和宽度,并将其显示偏移量基于这些值。但是,在使图像尚未完全加载的情况下使其成为ImageObserver时,我在构造函数中泄漏了this

public class Sprite extends SimplePaintable implements ImageObserver {

    private final Image sprite;
    private int xOffset;
    private boolean xOffsetSet;
    private int yOffset;
    private boolean yOffsetSet;

    public Sprite(Image sprite) {
        this.sprite = sprite;
        //warning: leaking this in constructor
        int width = sprite.getWidth(this);
        xOffset = width/2;
        xOffsetSet = width != -1;
        int height = sprite.getHeight(this);
        yOffset = height/2;
        yOffsetSet = height != -1;
    }

    @Override
    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
        assert img == sprite;
        if ((infoflags & WIDTH) != 0) {
            xOffset = width / 2;
            xOffsetSet = true;
        } 
        if ((infoflags & HEIGHT) != 0) {
            yOffset = height / 2;
            yOffsetSet = true;
        }
        return !(xOffsetSet && yOffsetSet);
    }
    ...

起初我认为这很好,因为只有偏移量变量未初始化且它们的默认值适合(不)显示卸载的图像,但后来我意识到如果图像立即加载getWidth(this)是在理论上,它可以在构造函数完成之前调用imageUpdate,导致在imageUpdate中正确设置偏移,然后由构造函数取消设置。这是一个问题,还是图像只在EDT上同步加载?如果这是一个问题,是否会使imageUpdate synchronized方法阻止它运行直到构造函数完成?

1 个答案:

答案 0 :(得分:1)

构造函数中的this泄漏只是一个警告,因为可能导致问题。如果super的变量已初始化,但this之后修改了它们,但super自身泄漏,则有人正在使用尚未完全初始化的变量。

如果您确定在构建时没有人访问任何变量,并且没有其他方法可以执行此操作(因为iE外部库阻止您以正确的方式执行),那么可以忽略警告。

在你的情况下理论上可能的是,在调用sprite.getWidth(this)期间,图像会立即调用观察者来更新进度,因此在构造函数完成之前调用imageUpdate。在这种情况下,偏移变量将在之后由构造函数初始化覆盖。不,同步不会阻止这个问题,因为在那一刻没有其他人持有锁。

有几种方法可以解决这个问题:

  • 使用BufferedImage,它不需要getWidth / getHeight的观察者。
    下行:图像必须满载,在某些情况下可能会引入一个小延迟(如果iE通过网络加载。

  • 用户正确锁定:

    private final Object offsetLock = new Object();
    
    public Sprite(Image sprite) {
        this.sprite = sprite;
    
        synchronized(offsetLock) {
            int width = sprite.getWidth(this);
            xOffset = width/2;
            xOffsetSet = width != -1;
            int height = sprite.getHeight(this);
            yOffset = height/2;
            yOffsetSet = height != -1;
        }
    }
    
    @Override
    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
        assert img == sprite;
        if ((infoflags & WIDTH) != 0) {
            synchronized(offsetLock) {
                xOffset = width / 2;
                xOffsetSet = true;
            }
        } 
        if ((infoflags & HEIGHT) != 0) {
            synchronized(offsetLock) {
                yOffset = height / 2;
                yOffsetSet = true;
            }
        }
        return xOffsetSet && yOffsetSet;
    }
    

    下行:这将调用synchronized,这可能会导致轻微的延迟。你通常不会注意到它只被调用了几次,但在一个时间关键的循环中,这可能是显而易见的。

  • 使用外部函数在构造完成后更新数据:

    public Sprite(Image sprite) {
        this.sprite = sprite;
    }
    
    protected updateOffsets() {
        updateWidth(sprite.getWidth(this));
        updateHeight(sprite.getHeight(this));
    }
    
    protected updateWidth(final int newWidth) {
        if (newWidth != -1) {
            xOffset = newWidth/2;
            xOffsetSet = true;
        }
    }
    
    protected updateHeight(final int newHeight) {
        if (newHeight!= -1) {
            yOffset = newHeight/2;
            yOffsetSet = true;
        }
    }
    
    @Override
    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
        assert img == sprite;
        if ((infoflags & WIDTH) != 0) {
            updateWidth(width);
        } 
        if ((infoflags & HEIGHT) != 0) {
            updateHeight(height);
        }
        return xOffsetSet && yOffsetSet;
    }
    

    缺点:有人必须调用updateOffsets()方法,在此之前对象未完全初始化,这可能会导致错误或要求您编写构建器方法,这会使事情变得更加复杂。

    < / LI>
相关问题