通过静态工厂确保java中的安全发布和线程安全

时间:2013-12-05 16:01:53

标签: java multithreading thread-safety immutability safe-publication

下面的课程是不可变的(但请参阅编辑):

public final class Position extends Data {

    double latitude;
    double longitude;
    String provider;

    private Position() {}

    private static enum LocationFields implements
            Fields<Location, Position, List<Byte>> {
        LAT {

            @Override
            public List<byte[]> getData(Location loc, final Position out) {
                final double lat = loc.getLatitude();
                out.latitude = lat;
                // return an arrayList
            }

            @Override
            public void parse(List<Byte> list, final Position pos)
                    throws ParserException {
                try {
                    pos.latitude = listToDouble(list);
                } catch (NumberFormatException e) {
                    throw new ParserException("Malformed file", e);
                }
            }
        }/* , LONG, PROVIDER, TIME (field from Data superclass)*/;

    }

    // ========================================================================
    // Static API (factories essentially)
    // ========================================================================
    public static  Position saveData(Context ctx, Location data) 
            throws IOException {
        final Position out = new Position();
        final List<byte[]> listByteArrays = new ArrayList<byte[]>();
        for (LocationFields bs : LocationFields.values()) {
            listByteArrays.add(bs.getData(data, out).get(0));
        }
        Persist.saveData(ctx, FILE_PREFIX, listByteArrays);
        return out;
    }

    public static List<Position> parse(File f) throws IOException,
            ParserException {
        List<EnumMap<LocationFields, List<Byte>>> entries;
        // populate entries from f
        final List<Position> data = new ArrayList<Position>();
        for (EnumMap<LocationFields, List<Byte>> enumMap : entries) {
            Position p = new Position();
            for (LocationFields field : enumMap.keySet()) {
                field.parse(enumMap.get(field), p);
            }
            data.add(p);
        }
        return data;
    }

    /**
     * Constructs a Position instance from the given string. Complete copy 
     * paste just to get the picture
     */
    public static Position fromString(String s) {
        if (s == null || s.trim().equals("")) return null;
        final Position p = new Position();
        String[] split = s.split(N);
        p.time = Long.valueOf(split[0]);
        int i = 0;
        p.longitude = Double.valueOf(split[++i].split(IS)[1].trim());
        p.latitude = Double.valueOf(split[++i].split(IS)[1].trim());
        p.provider = split[++i].split(IS)[1].trim();
        return p;
    }
}

不可变,它也是线程安全的。正如您所看到的,构建此类实例的唯一方法 - 除了反射这是另一个问题 - 实际上是使用提供的静态工厂。

问题

  • 是否有任何此类对象可能被不安全地发布?
  • 是否存在返回的对象是线程不安全的?

编辑:请不要评论字段不是私有的 - 我意识到这不是字典中的不可变类,但是包在我的控制之下,我永远不会改变它手动的一个字段的值(在构造之后)。没有提供变异器。

另一方面,不是最终的字段是问题的要点。当然我意识到,如果它们是最终的,那么类将是真正的不可变和线程安全(至少在Java5之后)。我希望在这种情况下提供一个不好用的例子。

最后 - 我并不是说 static 的工厂与线程安全有关,因为一些评论似乎暗示了。 重要的是,创建此类实例的唯一方法是通过那些(当然是静态的)工厂。

3 个答案:

答案 0 :(得分:2)

是的,可以不安全地发布此类的实例。这个类不是不可变的,所以如果实例化线程使一个实例可用于没有内存屏障的其他线程,那么这些线程可能会看到实例处于部分构造或其他不一致的状态。

您正在寻找的术语实际上是不可变的:实例字段可以在初始化后进行修改,但事实上它们不是。

这些对象可以被多个线程安全地使用,但这一切都取决于其他线程如何访问实例(即它们如何发布)。如果将这些对象放在并发队列上以供另一个线程使用 - 没问题。如果将它们分配给同步块中另一个线程可见的字段,并notify() wait()一个读取它们的线程 - 没问题。如果你在一个线程中创建所有实例然后启动使用它们的新线程 - 没问题!

但是,如果你只是将它们分配给一个非易失性字段,有时“稍后”另一个线程恰好读取该字段,那就是一个问题!写入线程和读取线程都需要同步点,以便在读取之前可以说真正的写入。

您的代码不会发布任何内容,因此我不能说您是否安全地执行此操作。您可以就此对象提出相同的问题:

class Option {

  private boolean value;

  Option(boolean value) { this.value = value; }

  boolean get() { return value; }

}

如果您在代码中做了一些“额外”的事情,您认为这会对您的对象的安全发布产生影响,请指出。

答案 1 :(得分:1)

位置不是不可变的,字段具有包可见性而不是最终的,请参见此处的不可变类的定义:http://www.javapractices.com/topic/TopicAction.do?Id=29

此外,由于这些字段不是最终字段,因此没有安全发布,因此没有其他机制来确保安全发布。许多地方都解释了安全出版的概念,但这一概念似乎特别相关:http://www.ibm.com/developerworks/java/library/j-jtp0618/ SO也有相关来源。

简而言之,安全发布是关于当您将构造实例的引用提供给另一个线程时会发生什么,该线程是否会按预期看到字段值?这里的答案是否定的,因为Java编译器和JIT编译器可以自由地使用引用发布重新排序字段初始化,从而导致半烘焙状态变为其他线程可见。

最后一点是至关重要的,从OP评论到下面的答案之一,他似乎认为静态方法在某种程度上与其他方法的工作方式不同,事实并非如此。静态方法可以像任何其他方法一样内联,构造函数也是如此(例外情况是构造函数在Java 1.5之后的最终字段)。需要明确的是,虽然JMM不保证构造是安全的,但它在某些甚至所有JVM上都可以正常工作。有关充分讨论,示例和行业专家意见,请参阅并发兴趣邮件列表中的讨论:http://jsr166-concurrency.10961.n7.nabble.com/Volatile-stores-in-constructors-disallowed-to-see-the-default-value-td10275.html

最重要的是,它可能会起作用,但根据JMM的说法,它并不安全。如果你不能证明它是安全的,那就不是。

答案 2 :(得分:0)

Position类的字段不是final,所以我认为构造函数不能安全地发布它们的值。因此构造函数不是线程安全的,因此使用它们的代码(例如工厂方法)不会产生线程安全的对象。