Java易失和/或同步

时间:2016-10-31 10:03:18

标签: java concurrency synchronized volatile

我有一个静态方法,它应该根据当前时间戳生成一个唯一的ID,如下面的代码所示。为了确保新生成的ID与先前生成的ID不同(由于非常快的计算机使得毫秒不会改变),我放入一个循环来比较新生成的ID与先前生成的ID。如果它们相同,它将生成另一个ID。

public class Util {

    protected static String uniqueID;

    public static String generateUniqueID() {
        SimpleDateFormat timstampFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        do {
            String timestamp = timstampFormat.format(new Date());
            if (!timestamp.equals(uniqueID)) {
                uniqueID = timestamp;
                return uniqueID;
            }
        } while (true);
    }

}

我希望上面的代码在多个线程调用方法时起作用。

如果我只是将volatile关键字放入uniqueID变量,那还不够好吗?我还需要同步块吗?

如果有一个同步块但没有volatile关键字呢?

提前致谢。

增加:

如果我更改为以下代码,仍然需要使用volatile关键字吗?

public class Util {

    private static volatile String uniqueID;

    public static synchronized String generateUniqueID() {
        uniqueID = UUID.randomUUID().toString();
        return uniqueID;
    }

}

4 个答案:

答案 0 :(得分:7)

此代码存在多个问题。

从不使用时间戳作为UID,除非您绝对肯定,否则在一小于时间戳的最低分辨率的时间内不会生成多个UID你正在使用。我建议改用完全不同的方法。如果您绝对想要使用时间戳格式或仅使用计数器,请将计数器附加到时间戳。除了正常的系统时间之外,另一种方法是使用System.nanoTime(),但可能会提供相当多的陷阱。

如果您尝试在同一毫秒内生成两个UID,则您的while循环将循环最多一整毫秒。没有快速的计算机可以完全浪费CPU时间。循环至少会运行几千次而没有适当的结果。

标记变量volatile不会。您必须标记在方法synchronized中运行的整个块,以防止多个线程同时运行它。但考虑一个案例,您希望在一个ms内生成1000个UID。现在应该做什么,现在突然间需要一整秒钟。你正在制造一个巨大的瓶颈。

我的建议:
立即删除该方法。没有太多可以修复此代码的程度,以至于它在性能和正确性方面实际上是可以接受的。阅读有关并发的this tutorial。获得一种生成UID的新方法,并从头开始。

或者:
为什么甚至为已经存在的东西编写代码?使用Oracle提供的UID-class。另一个好方法是使用UUID,它是实用程序包的一部分,很可能比UID更通用。取决于您对生成的UID的要求。

答案 1 :(得分:2)

你有一个问题,你忙着等待每毫秒,如果多个线程在等待,他们都会等待,可能是无休止的。无论您如何提供线程安全,都会发生这种情况。更好的方法是使用一个总是> =时间的计数器或一个比当前时间多很多倍的计数器。

你可以做到

x % 1000

这将为您提供每毫秒的id,无需等待。如果您在同一毫秒内有多个ID,则将来会有一些ID。如果需要,可以将其转换为格式的字符串。这仍然存在一个问题,即你不可能每毫秒超过1毫秒而不会从当前时间漂移。

synchronized

这为您提供了一个唯一的ID,当前时间为1000x。这意味着每毫秒可以有1000个ID而不会漂移,当您转换为String时,需要除以1000以获得以毫秒为单位的时间,并将volatile作为三位数附加到最后。

更简单的折衷方案可能是乘以10.这样可以得到每毫秒10个ID。

  

如果通过synchronized块修改/访问实例变量,这是否意味着所有线程都可以看到最新修改。

同步块使用CPU实现的读/写内存屏障来确保发生这种情况。

注意:如果您使用同步,则不需要使用volatile。事实上,使用两者可能会更慢。

  

如果我更改为以下代码,仍然需要使用volatile关键字吗?

当您使用共享的变量时,需要

private static String uniqueID; public static synchronized String generateUniqueID() { uniqueID = UUID.randomUUID().toString(); // without synchronized, one thread could read what another thread wrote return uniqueID; } // nothing is shared, so no thread safety issues. public static String generateUniqueID() { return UUID.randomUUID().toString(); } 是多余的,并没有帮助。

NoMethodError in PostsController#show

如果使用局部变量,则不需要同步或易失性,因为没有共享。

PostsController

答案 2 :(得分:1)

与其他人一样,我强烈建议摆脱繁忙循环,时间戳作为标识符以及同步易失性思维。易失性不会起作用,同步会起作用,但会非常低效。

但我建议的是

  • 使用compareAndSet of AtomicReference进行检查,因为它可以使用CPU支持的实际原子操作
  • 使用未按时绑定的标识符,这样可以避免等待时间更改的瓶颈
  • 如果您坚持使用时间戳作为标识符,等待时至少sleep

答案 3 :(得分:1)

您的字段uniqueID必须为volatile,以确保所有主题都能看到最新修改,您应该依赖UUID(代表Universally unique identifier)来生成唯一ID(由于它依赖于日期时间而且还依赖于MAC地址,在集群中甚至是唯一的),您的代码将成为下一个:

public static String generateUniqueID() {
    return UUID.randomUUID().toString();
}
  

public static UUID randomUUID()

     

静态工厂检索类型4(伪随机生成)UUID。   使用加密强伪随机生成UUID   数字生成器。