我司核心系统在高并发情况下,原毫秒时间戳分辨率不足,需要获取分辨率更高的微秒时间戳,且生产系统几乎不具备将目前正在使用的JDK1.8进行升级的可能。
一、Java8获取高精度时间戳的限制
由于交易流水号的存在,之前用到高精度时间戳的场景不多。一阵搜索后发现:Java8中获取到的当前时刻仅限于毫秒级,尽管java.time中的类可以【保存】以纳秒为单位的值,但却只能以毫秒为单位【获取】当前时间。这种限制是由于java.time.Clock的底层实现决定的,在Java9及更高版本中,新的java.time.Clock实现可以以更精细的分辨率获取当前时刻,具体取决于主机硬件和操作系统的限制。
使用如下代码初步测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.time.Instant; import java.time.LocalDateTime; public class NanoTest2 { public static void main(String args[]) throws InterruptedException { for (int i = 0; i < 11; i++) { System.out.println(System.currentTimeMillis()); } for (int i = 0; i < 14; i++) { System.out.println(System.nanoTime()); } for (int i = 0; i < 11; i++) { System.out.println(LocalDateTime.now()); } for (int i = 0; i < 8; i++) { System.out.println(Instant.now().toString()); } } } |
在JDK 1.8上编译为class文件,然后分别在Java8和Java11下测试:
同样的class文件在Java11上执行:
可见,在Java8中获取到的时间到毫秒后就直接截断了,而在Java11中则输出到了微秒级别。而在java.time中的几个类也正在逐渐取代原有的java.util.Date:
接着大概看一下上述用到的几个函数。
System.nanoTime()是一个native函数,JDK源码注释如下:
需要注意,System.nanoTime()与System.currentTimeMillis()显著不同,System.currentTimeMillis()我们肯定非常熟悉了,返回的是:
the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC
而System.nanoTime()根据文档所述,返回的则是当前虚拟机距离某个固定但随机的起始时间(这个起始时间可能是未来的时间,因此System.nanoTime()可能返回负数)的纳秒数,该起始时间在同一个虚拟机内部是相同的,但在不同的虚拟机之间则是不同的。因此,单独获取System.nanoTime()并没有什么意义,因为该值是随机的,通常只能用于多次获取System.nanoTime()之差来测量逝去的时间,而与任何其它系统或者真实的时间概念无关。
在某些文章中曾不断提到“System.nanoTime()非线程安全”的说法,如参考文档1中2018年这类较新的文章中还是错的:
这个说法实际是错误的,根据Stack Overflow中的说法,自Java7以后System.nanoTime()就是线程安全的了:
LocalDateTime.now()返回是:
the current date-time from the system clock in the default time-zone
根据文档,LocalDateTime.now()的返回值也是java.time.LocalDateTime类型的:
正如前面的表格所示,java.time.LocalDateTime及其内部的成员变量java.time.LocalDate和java.time.LocalTime都不带时区信息。
java.time.Instant是JDK1.8后引入的一个类,下面是其JDK1.8源码中的注释及两个关键方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
/** * An instantaneous point on the time-line. * <p> * This class models a single instantaneous point on the time-line. * This might be used to record event time-stamps in the application. * <p> * The range of an instant requires the storage of a number larger than a {@code long}. * To achieve this, the class stores a {@code long} representing epoch-seconds and an * {@code int} representing nanosecond-of-second, which will always be between 0 and 999,999,999. * The epoch-seconds are measured from the standard Java epoch of {@code 1970-01-01T00:00:00Z} * where instants after the epoch have positive values, and earlier instants have negative values. * For both the epoch-second and nanosecond parts, a larger value is always later on the time-line * than a smaller value. * * <h3>Time-scale</h3> * <p> * The length of the solar day is the standard way that humans measure time. * This has traditionally been subdivided into 24 hours of 60 minutes of 60 seconds, * forming a 86400 second day. * <p> * Modern timekeeping is based on atomic clocks which precisely define an SI second * relative to the transitions of a Caesium atom. The length of an SI second was defined * to be very close to the 86400th fraction of a day. * <p> * Unfortunately, as the Earth rotates the length of the day varies. * In addition, over time the average length of the day is getting longer as the Earth slows. * As a result, the length of a solar day in 2012 is slightly longer than 86400 SI seconds. * The actual length of any given day and the amount by which the Earth is slowing * are not predictable and can only be determined by measurement. * The UT1 time-scale captures the accurate length of day, but is only available some * time after the day has completed. * <p> * The UTC time-scale is a standard approach to bundle up all the additional fractions * of a second from UT1 into whole seconds, known as <i>leap-seconds</i>. * A leap-second may be added or removed depending on the Earth's rotational changes. * As such, UTC permits a day to have 86399 SI seconds or 86401 SI seconds where * necessary in order to keep the day aligned with the Sun. * <p> * The modern UTC time-scale was introduced in 1972, introducing the concept of whole leap-seconds. * Between 1958 and 1972, the definition of UTC was complex, with minor sub-second leaps and * alterations to the length of the notional second. As of 2012, discussions are underway * to change the definition of UTC again, with the potential to remove leap seconds or * introduce other changes. * <p> * Given the complexity of accurate timekeeping described above, this Java API defines * its own time-scale, the <i>Java Time-Scale</i>. * <p> * The Java Time-Scale divides each calendar day into exactly 86400 * subdivisions, known as seconds. These seconds may differ from the * SI second. It closely matches the de facto international civil time * scale, the definition of which changes from time to time. * <p> * The Java Time-Scale has slightly different definitions for different * segments of the time-line, each based on the consensus international * time scale that is used as the basis for civil time. Whenever the * internationally-agreed time scale is modified or replaced, a new * segment of the Java Time-Scale must be defined for it. Each segment * must meet these requirements: * <ul> * <li>the Java Time-Scale shall closely match the underlying international * civil time scale;</li> * <li>the Java Time-Scale shall exactly match the international civil * time scale at noon each day;</li> * <li>the Java Time-Scale shall have a precisely-defined relationship to * the international civil time scale.</li> * </ul> * There are currently, as of 2013, two segments in the Java time-scale. * <p> * For the segment from 1972-11-03 (exact boundary discussed below) until * further notice, the consensus international time scale is UTC (with * leap seconds). In this segment, the Java Time-Scale is identical to * <a href="http://www.cl.cam.ac.uk/~mgk25/time/utc-sls/">UTC-SLS</a>. * This is identical to UTC on days that do not have a leap second. * On days that do have a leap second, the leap second is spread equally * over the last 1000 seconds of the day, maintaining the appearance of * exactly 86400 seconds per day. * <p> * For the segment prior to 1972-11-03, extending back arbitrarily far, * the consensus international time scale is defined to be UT1, applied * proleptically, which is equivalent to the (mean) solar time on the * prime meridian (Greenwich). In this segment, the Java Time-Scale is * identical to the consensus international time scale. The exact * boundary between the two segments is the instant where UT1 = UTC * between 1972-11-03T00:00 and 1972-11-04T12:00. * <p> * Implementations of the Java time-scale using the JSR-310 API are not * required to provide any clock that is sub-second accurate, or that * progresses monotonically or smoothly. Implementations are therefore * not required to actually perform the UTC-SLS slew or to otherwise be * aware of leap seconds. JSR-310 does, however, require that * implementations must document the approach they use when defining a * clock representing the current instant. * See {@link Clock} for details on the available clocks. * <p> * The Java time-scale is used for all date-time classes. * This includes {@code Instant}, {@code LocalDate}, {@code LocalTime}, {@code OffsetDateTime}, * {@code ZonedDateTime} and {@code Duration}. * * <p> * This is a <a href="{@docRoot}/java/lang/doc-files/ValueBased.html">value-based</a> * class; use of identity-sensitive operations (including reference equality * ({@code ==}), identity hash code, or synchronization) on instances of * {@code Instant} may have unpredictable results and should be avoided. * The {@code equals} method should be used for comparisons. * * @implSpec * This class is immutable and thread-safe. * * @since 1.8 */ public final class Instant implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable { /** * Constant for the 1970-01-01T00:00:00Z epoch instant. */ public static final Instant EPOCH = new Instant(0, 0); /** * The minimum supported epoch second. */ private static final long MIN_SECOND = -31557014167219200L; /** * The maximum supported epoch second. */ private static final long MAX_SECOND = 31556889864403199L; /** * The minimum supported {@code Instant}, '-1000000000-01-01T00:00Z'. * This could be used by an application as a "far past" instant. * <p> * This is one year earlier than the minimum {@code LocalDateTime}. * This provides sufficient values to handle the range of {@code ZoneOffset} * which affect the instant in addition to the local date-time. * The value is also chosen such that the value of the year fits in * an {@code int}. */ public static final Instant MIN = Instant.ofEpochSecond(MIN_SECOND, 0); /** * The maximum supported {@code Instant}, '1000000000-12-31T23:59:59.999999999Z'. * This could be used by an application as a "far future" instant. * <p> * This is one year later than the maximum {@code LocalDateTime}. * This provides sufficient values to handle the range of {@code ZoneOffset} * which affect the instant in addition to the local date-time. * The value is also chosen such that the value of the year fits in * an {@code int}. */ public static final Instant MAX = Instant.ofEpochSecond(MAX_SECOND, 999_999_999); /** * Serialization version. */ private static final long serialVersionUID = -665713676816604388L; /** * The number of seconds from the epoch of 1970-01-01T00:00:00Z. */ private final long seconds; /** * The number of nanoseconds, later along the time-line, from the seconds field. * This is always positive, and never exceeds 999,999,999. */ private final int nanos; //----------------------------------------------------------------------- /** * Obtains the current instant from the system clock. * <p> * This will query the {@link Clock#systemUTC() system UTC clock} to * obtain the current instant. * <p> * Using this method will prevent the ability to use an alternate time-source for * testing because the clock is effectively hard-coded. * * @return the current instant using the system clock, not null */ public static Instant now() { return Clock.systemUTC().instant(); } /** * Obtains the current instant from the specified clock. * <p> * This will query the specified clock to obtain the current time. * <p> * Using this method allows the use of an alternate clock for testing. * The alternate clock may be introduced using {@link Clock dependency injection}. * * @param clock the clock to use, not null * @return the current instant, not null */ public static Instant now(Clock clock) { Objects.requireNonNull(clock, "clock"); return clock.instant(); } } |
从中我们可以总结出:
- Instant是不可变且线程安全的;
- Instant内部有两个long类型的成员,分别保存了自1970-01-01T00:00:00Z后的秒数,及该秒对应的纳秒(大于0,小于999,999,999);
- Instant.now()调用的是Clock.systemUTC().instant(),因此也就对应上前述受限于Clock的问题。从JDK1.8(Oracle)的源码中我们可以看到:
可见在JDK1.8上,Instant确实最多也就只能达到毫秒的级别。接下来让我们看下JDK1.9(OpenJDK)的源码:
很明显,JDK1.9上的Clock在返回Instant时增加了对毫秒的处理,getNanoTimeAdjustment是一个native方法,就是用于获取纳秒的:
1 |
public static native long getNanoTimeAdjustment(long offsetInSeconds); |
Instant.now().toString()输出的是ISO-8601格式的UTC时间戳,Java8及以下不输出微秒,Java9以上输出到微秒级别。
二、NanoClock方案
由于升级JDK版本需要大量测试,目前的条件下基本是不切实际的,因此需要寻求替代方案。
笔者的第一反应是,JDK1.8没戏了。既然JDK层面受限,那么直接通过JNI调用C实现好了,而确实也有人这么做了:
不过,杜老板说:
也就是参考资料3中的实现。不过,这个帖子下面有一个评论:
其中提到所谓的precision不等于accuracy,实际是System.nanoTime()的注释中提到的:
This method provides nanosecond precision, but not necessarily nanosecond resolution (that is, how frequently the value changes) – no guarantees are made except that the resolution is at least as good as that of currentTimeMillis().
不过这应该不影响我们的使用。其完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public class NanoClock extends Clock { private final Clock clock; private final long initialNanos; private final Instant initialInstant; public NanoClock() { this(Clock.systemUTC()); } public NanoClock(final Clock clock) { this.clock = clock; initialInstant = clock.instant(); initialNanos = getSystemNanos(); } @Override public ZoneId getZone() { return clock.getZone(); } @Override public Instant instant() { return initialInstant.plusNanos(getSystemNanos() - initialNanos); } @Override public Clock withZone(final ZoneId zone) { return new NanoClock(clock.withZone(zone)); } private long getSystemNanos() { synchronized (clock) { return System.nanoTime(); } } } |
三、NanoClock线程安全测试
接下来,需要验证多线程情况下获取到的时间戳确实是严格趋势递增的。
首先,从原理上看,NanoClock的本质就是自己记录了开始时刻,并利用System.nanoTime()的特性来实现计算出流逝的纳秒数,其核心逻辑是:
1 2 3 4 5 |
@Override public Instant instant() { return initialInstant.plusNanos(getSystemNanos() - initialNanos); } |
plusNanos内部有一些逻辑,但基本都是线程安全的数学运算。
接下来,写代码实测。直觉上,我们直接使用AtomicInteger依次对NanoClock获取的时间戳计数,丢到线程安全的map里,然后利用TreeMap排序即可,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class NanoTest1 { public static class NanoClock extends Clock { private final Clock clock; private final long initialNanos; private final Instant initialInstant; public NanoClock() { this(Clock.systemUTC()); } public NanoClock(final Clock clock) { this.clock = clock; initialInstant = clock.instant(); initialNanos = getSystemNanos(); } @Override public ZoneId getZone() { return clock.getZone(); } @Override public Instant instant() { return initialInstant.plusNanos(getSystemNanos() - initialNanos); } @Override public Clock withZone(final ZoneId zone) { return new NanoClock(clock.withZone(zone)); } private long getSystemNanos() { return System.nanoTime(); } } public static void main(String args[]) throws InterruptedException { int size = 10; int loop = 10000; Executor executor = Executors.newFixedThreadPool(size); CountDownLatch countDownLatch = new CountDownLatch(size); final Clock clock = new NanoClock(); AtomicInteger atomicInteger = new AtomicInteger(); Map<Integer, String> map = new ConcurrentHashMap<>(); for (int i = 0; i < size; i++) { Runnable runnable = new Runnable() { int i = 0; @Override public void run() { while (i++ < loop) { Instant instant = Instant.now(clock); map.put(atomicInteger.incrementAndGet(), instant.toString()); } System.out.println("跑完"); countDownLatch.countDown(); } }; ((ExecutorService) executor).submit(runnable); } countDownLatch.await(); //排序 TreeMap<Integer, String> treeMap = new TreeMap<>(map); Instant oldInstant = null; for (Map.Entry e : treeMap.entrySet()) { Instant instant = Instant.parse(e.getValue().toString()); System.out.println(instant.toString()); if (oldInstant != null && instant.isBefore(oldInstant)) { System.out.println("You are fucked"); } oldInstant = instant; } } } |
然而,上述的代码却会发现大量乱序的情况出现,例如:
推测可能是由于AtomicInteger为非公平锁导致:
那么,我们换一种方式,改成用LinkedBlockingQueue,利用队列先进先出的特性,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
import java.sql.Timestamp; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Queue; import java.util.concurrent.*; public class NanoTest0 { public static class NanoClock extends Clock { private final Clock clock; private final long initialNanos; private final Instant initialInstant; public NanoClock() { this(Clock.systemUTC()); } public NanoClock(final Clock clock) { this.clock = clock; initialInstant = clock.instant(); initialNanos = getSystemNanos(); } @Override public ZoneId getZone() { return clock.getZone(); } @Override public Instant instant() { return initialInstant.plusNanos(getSystemNanos() - initialNanos); } @Override public Clock withZone(final ZoneId zone) { return new NanoClock(clock.withZone(zone)); } private long getSystemNanos() { return System.nanoTime(); } } public static void main(String args[]) throws InterruptedException { int size = 10; int loop = 10000; Executor executor = Executors.newFixedThreadPool(size); CountDownLatch countDownLatch = new CountDownLatch(size); final Clock clock = new NanoClock(); Queue<Instant> queue = new LinkedBlockingQueue<>(); for (int i = 0; i < size; i++) { Runnable runnable = new Runnable() { int i = 0; Object object = new Object(); @Override public void run() { while (i++ < loop) { try { Instant instant = Instant.now(clock); ((LinkedBlockingQueue<Instant>) queue).put(instant); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("跑完"); countDownLatch.countDown(); } }; ((ExecutorService) executor).submit(runnable); } countDownLatch.await(); Instant oldInstant = null; while (!queue.isEmpty()) { Instant instant = ((LinkedBlockingQueue<Instant>) queue).take(); Timestamp timestamp = Timestamp.from(instant); System.out.println(instant.toString()); System.out.println(timestamp); if (oldInstant != null && instant.isBefore(oldInstant)) { System.out.println("You are fucked"); } oldInstant = instant; } } } |
结果依然有乱序的情况发生。猜测LinkedBlockingQueue的锁也是非公平的,于是换为可配置锁公平性的ArrayBlockingQueue:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Queue; import java.util.concurrent.*; public class NanoTest1 { public static class NanoClock extends Clock { private final Clock clock; private final long initialNanos; private final Instant initialInstant; public NanoClock() { this(Clock.systemUTC()); } public NanoClock(final Clock clock) { this.clock = clock; initialInstant = clock.instant(); initialNanos = getSystemNanos(); } @Override public ZoneId getZone() { return clock.getZone(); } @Override public Instant instant() { synchronized (clock) { return initialInstant.plusNanos(getSystemNanos() - initialNanos); } } @Override public Clock withZone(final ZoneId zone) { return new NanoClock(clock.withZone(zone)); } private long getSystemNanos() { synchronized (clock) { return System.nanoTime(); } } } public static void main(String args[]) throws InterruptedException { int size = 10; int loop = 10000; Executor executor = Executors.newFixedThreadPool(size); CountDownLatch countDownLatch = new CountDownLatch(size); final Clock clock = new NanoClock(); Queue<Instant> queue = new ArrayBlockingQueue<>(size * loop, true); for (int i = 0; i < size; i++) { Runnable runnable = new Runnable() { int i = 0; @Override public void run() { while (i++ < loop) { try { Instant instant = Instant.now(clock); ((ArrayBlockingQueue<Instant>) queue).put(instant); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("跑完"); countDownLatch.countDown(); } }; ((ExecutorService) executor).submit(runnable); } countDownLatch.await(); Instant oldInstant = null; while (!queue.isEmpty()) { Instant instant = ((ArrayBlockingQueue<Instant>) queue).take(); System.out.println(instant.toString()); if (oldInstant != null && instant.isBefore(oldInstant)) { System.out.println("You are fucked"); } oldInstant = instant; } System.out.println("<<<<<<<<<跑完>>>>>>>>>"); } } |
然而,即便使用了公平锁的队列,依然有乱序的情况发生,只不过概率低了一点。
感觉十分诡异,仔细分析代码的执行行为。推测可能是从获取到调用时刻与入队的逻辑有关系:
1 2 |
Instant instant = Instant.now(clock); ((ArrayBlockingQueue<Instant>) queue).put(instant); |
即假设两个线程A和B同时调用了 Instant.now(clock),线程A先执行System.nanoTime()获取到了纳秒数,线程B后执行System.nanoTime()获取到了纳秒数,由于plusNanos内部还有不少的计算逻辑,因此在发生上下文切换等时,B对 Instant.now(clock)的调用可能反而先于A对 Instant.now(clock)的调用返回,进而导致靠后的时间戳反而先入队列。我们构造如下的代码验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class NanoTest5 { public static AtomicInteger errorCount1 = new AtomicInteger(); public static AtomicInteger errorCount2 = new AtomicInteger(); public static class NanoClock extends Clock { private final Clock clock; private final long initialNanos; private final Instant initialInstant; public NanoClock() { this(Clock.systemUTC()); } public NanoClock(final Clock clock) { this.clock = clock; initialInstant = clock.instant(); initialNanos = getSystemNanos(); } @Override public ZoneId getZone() { return clock.getZone(); } @Override public Instant instant() { return initialInstant.plusNanos(getSystemNanos() - initialNanos); } @Override public Clock withZone(final ZoneId zone) { return new NanoClock(clock.withZone(zone)); } private long getSystemNanos() { synchronized (clock) { return System.nanoTime(); } } } public static void main(String args[]) throws InterruptedException { int size = 10; int loop = 10000; Executor executor = Executors.newFixedThreadPool(size); CountDownLatch countDownLatch = new CountDownLatch(size); final Clock clock = new NanoClock(); Queue<Instant> queue1 = new ArrayBlockingQueue<>(size * loop, true); Queue<Integer> queue2 = new ArrayBlockingQueue<>(size * loop, true); Queue<Integer> queue3 = new ArrayBlockingQueue<>(size * loop, true); for (int i = 0; i < size * loop; i++) { ((ArrayBlockingQueue<Integer>) queue2).put(i); ((ArrayBlockingQueue<Integer>) queue3).put(i); } for (int i = 0; i < size; i++) { Runnable runnable = new Runnable() { int i = 0; @Override public void run() { while (i++ < loop) { try { Integer startIndex = ((ArrayBlockingQueue<Integer>) queue2).take(); Instant instant = Instant.now(clock); Integer endIndex = ((ArrayBlockingQueue<Integer>) queue3).take(); if (startIndex.intValue() != endIndex.intValue()) { errorCount1.incrementAndGet(); System.out.println("####################" + startIndex + " " + endIndex); } ((ArrayBlockingQueue<Instant>) queue1).put(instant); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("跑完"); countDownLatch.countDown(); } }; ((ExecutorService) executor).submit(runnable); } countDownLatch.await(); Instant oldInstant = null; while (!queue1.isEmpty()) { Instant instant = ((ArrayBlockingQueue<Instant>) queue1).take(); System.out.println(instant.toString()); if (oldInstant != null && instant.isBefore(oldInstant)) { errorCount2.incrementAndGet(); System.out.println("You are fucked"); } oldInstant = instant; } System.out.println("<<<<<<<<<跑完>>>>>>>>>"); System.out.println("e1=" + NanoTest4.errorCount1.intValue()); System.out.println("e2=" + NanoTest4.errorCount2.intValue()); } } |
这样,为了来规避这个问题,就不能使用队列了。我们依然在获取时间的过程中使用Map,并在最后利用TreeMap排序,不过根据NanoClock的原理,需要以真正获取到System.nanoTime()的顺序为准。此外,我们还必须注意,不能使用诸如:
1 2 3 |
Integer startIndex = ((ArrayBlockingQueue<Integer>) queue2).take(); Instant instant = Instant.now(clock); ((ArrayBlockingQueue<Instant>) queue1).put(instant); |
这样的逻辑,因为先获取到“序号”的线程不一定会真正先调用到System.nanoTime()。调整后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Map; import java.util.Queue; import java.util.TreeMap; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class NanoTest3 { public static AtomicInteger errorCount = new AtomicInteger(); static int size = 1; static int loop = 10000; static Queue<Instant> queue1 = new ArrayBlockingQueue<>(size * loop + 1, true); static Queue<Integer> queue2 = new ArrayBlockingQueue<>(size * loop + 1, true); static Queue<Integer> queue3 = new ArrayBlockingQueue<>(size * loop + 1, true); static Map<Integer, Instant> map = new ConcurrentHashMap<>(); static { for (int i = 0; i < size * loop + 1; i++) { try { ((ArrayBlockingQueue<Integer>) queue2).put(i); ((ArrayBlockingQueue<Integer>) queue3).put(i); } catch (InterruptedException e) { e.printStackTrace(); } } } public static class NanoClock extends Clock { static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); private final Clock clock; private final long initialNanos; private final Instant initialInstant; public NanoClock() { this(Clock.systemUTC()); } public NanoClock(final Clock clock) { this.clock = clock; initialInstant = clock.instant(); initialNanos = getSystemNanos(); } @Override public ZoneId getZone() { return clock.getZone(); } @Override public Instant instant() { Instant instant = initialInstant.plusNanos(getSystemNanos() - initialNanos); Integer integer = threadLocal.get(); map.put(integer, instant); threadLocal.remove(); return instant; } @Override public Clock withZone(final ZoneId zone) { return new NanoClock(clock.withZone(zone)); } private long getSystemNanos() { long l = System.nanoTime(); try { Integer startIndex = ((ArrayBlockingQueue<Integer>) queue2).take(); threadLocal.set(startIndex); } catch (InterruptedException e) { e.printStackTrace(); } return l; } } public static void main(String args[]) throws InterruptedException { Executor executor = Executors.newFixedThreadPool(size); CountDownLatch countDownLatch = new CountDownLatch(size); final Clock clock = new NanoClock(); for (int i = 0; i < size; i++) { Runnable runnable = new Runnable() { int i = 0; @Override public void run() { while (i++ < loop) { Instant instant = Instant.now(clock); } System.out.println("跑完"); countDownLatch.countDown(); } }; ((ExecutorService) executor).submit(runnable); } countDownLatch.await(); //排序 TreeMap<Integer, Instant> treeMap = new TreeMap<>(map); Instant oldInstant = null; for (Map.Entry e : treeMap.entrySet()) { Instant instant = Instant.parse(e.getValue().toString()); System.out.println(instant.toString()); if (oldInstant != null && instant.isBefore(oldInstant)) { NanoTest3.errorCount.incrementAndGet(); System.out.println("You are fucked"); } oldInstant = instant; } System.out.println("<<<<<<<<<跑完>>>>>>>>>"); System.out.println("e=" + NanoTest3.errorCount.intValue()); } } |
验证无问题,NanoClock线程安全且严格递增。
三、数据库存储
首先明确,java.util.Data只能保存毫秒精度,因此如果DAO层使用的是此种类型必然将导致精度丢失:
对于MySQL而言,DATETIME和TIMESTAMP(这两者的区别不再赘述)均可以保存到微秒的精度(6位)。文档中提到,即使传入了精度更高的值,值不会被丢弃而是会被保存下来,可见后续应该是能够支持到纳秒的;不过,目前的5.7和8.0均只支持到微秒:
java.sql.Timestamp支持纳秒:
我们可以直接通过如下的方式将java.time.Instant转换为java.sql.Timestamp并用于后续的持久化:
1 |
Timestamp timestamp = Timestamp.from(instant); |
参考文档:
1、https://www.geeksforgeeks.org/java-system-nanotime-vs-system-currenttimemillis/
2、https://blog.csdn.net/weixin_29796279/article/details/114247510
3、https://stackoverflow.com/questions/20689055/java-8-instant-now-with-nanosecond-resolution
转载时请保留出处,违法转载追究到底:进城务工人员小梅 » JDK1.8中线程安全地获取微秒/纳秒时间戳及锁的公平性问题
太棒了~