Java中同步块的替代方法

11

我使用以下代码来确保 startTime 变量仅设置一次:

public class Processor
{
    private Date startTime;

    public void doProcess()
    {
        if(startTime == null)
            synchronized(this)
            {
                  if(startTime == null)
                  {
                     startTime = new Date();
                  }
            }

        // do somethings
    }
}

我将用这段代码来保证变量只被实例化一次,无论调用多少次process方法。

我的问题是:

是否有更简洁的替代方法?(例如可以删除ifsynchronized语句)


我认为你可以使用原子引用:http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/atomic/AtomicReference.html - Suzan Cioc
6个回答

12

使用 AtomicReference

public class Processor {
  private final AtomicReference<Date> startTime = new AtomicReference<Date>();
  public void doProcess() {
    if (this.startTime.compareAndSet(null, new Date())) {
      // do something first time only
    }
    // do somethings
  }
}

@yegor256 我不明白这是怎么工作的?我知道 getAndSet 方法会返回旧值,然后将传递的新值设置进去。 - Sam
@yegor256 我明白你的建议,谢谢,但是它仍然会在每次调用doProcess方法时创建一个 Date 对象。 - Sam
Date 的实例化是一项廉价操作,查看 JDK 中类的实现方式:它具有非常小的内存占用。 - yegor256
@yegor256 您是正确的,但我们必须尝试编写理念代码。在我的应用程序中,doProcess 可能每分钟触发 100 次,因此我必须找到最佳方法。 - Sam
1
你可以将if块更改为if((startTime.get() == null) && startTime.compareAndSet(null, new Date())) - jtahlborn
显示剩余2条评论

11

根据您的评论,您可以使用AtomicReference。

firstStartTime.compareAndSet(null, new Date());
或者AtomicLong
firstStartTime.compareAndSet(0L, System.currentTimeMillis());
我会使用:
private final Date startTime = new Date();
或者
private final long startTime = System.currentTimeMillis();

1
这个想法似乎是在 doProcess 被调用时触发 startTime... 所以我猜你是建议在创建 Processor 对象时启动计时器。 - assylias
@PeterLawrey 在应用程序启动时创建了对象,但是只有在应用程序接收到事务(即最终用户执行特定操作)时才会调用doProcess - Sam
@PeterLawrey 不,我希望在第一个用户触发进程时初始化此变量。 - Sam
1
+1 这完全符合OP的需求。恰当的语义,没有对象浪费,完全发挥了速度优势。 - Marko Topolnik
1
MJM,如果不加锁,你就无法得到更好的结果。如果涉及到非常非常重的东西,比如比“Date”重一百万倍,那么你实际上需要更多的东西。在这种情况下,你应该阅读有关正确实现双重检查习惯用法的文章,例如这里。这是Josh Bloch亲口说的。对于你的要求,这种习惯用法并不完全适合,因为它超出了很多数量级的范围。事实上,每分钟100次调用甚至也是过度的。 - Marko Topolnik
显示剩余10条评论

4

您的代码是所谓的“双重检查锁定”的示例。请阅读这篇文章,它解释了为什么这个技巧在Java中不起作用,尽管它非常聪明。


Brien Goetz非常聪明,但那篇文章已经11年了。自那时以来,JVM肯定发生了很大变化。我想知道是否有更近期的文章涉及JVM 6或更新版本。 - duffymo
1
@duffymo 语义没有改变,这种习惯用法仍然是失败的。唯一相关的变化是在Java 1.5中发生的volatile的固定语义。顺便说一下,volatile正是OP需要修复此问题的东西。 - Marko Topolnik

3
据我理解,您需要一个单例模式,它应该具备以下特点:
  1. 短小精悍,易于实现和理解。
  2. 只有在调用 doProcess 方法时才会初始化。
我建议使用嵌套类来实现如下代码:
public class Processor {
    private Date startTime;

    private static class Nested {
        public static final Date date = new Date();
    }

    public void doProcess() {
        startTime = Nested.date; // initialized on first reference
        // do somethings
    }
}

你的回答很好,但我想要减少我的代码行数(LOC);) 我点赞支持这个。 - Sam

2
总结其他帖子已经解释的内容:
private volatile Date startTime;

public void doProcess()
{
   if(startTime == null) startTime = new Date();
   // ...
}

足够简洁吗?

@Alex 我先写了你的建议,但仔细思考后发现它不是线程安全的。 - Sam
唯一的危险是多个线程可能会冗余地初始化“startTime”,这不会导致任何不良后果。 - Alex D
1
顺便提一下,java.lang.String 对于延迟初始化其缓存哈希值采用相同的策略。如果上面的代码“不是线程安全的”,那就意味着java.lang.String也“不是线程安全的”。 - Alex D

0

1 你所使用的是被称为双重检查锁定

2. 还有另外两种方法可以实现。

  - Use synchronized on the Method
  - Initialize the static variable during declaration.

3. 由于您想要一个没有ifsynchronized关键字的示例,我将展示您使用在声明期间初始化静态变量的方式。

public class MyClass{

  private static MyClass unique = new MyClass();

  private MyClass{}

  public static MyClass getInstance(){

      return unique;

  }

 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接