有用但不为大家所熟知的 Java 特性

发表于:2022-8-16 09:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Trazen    来源:稀土掘金

  在本文中,你可以了解一些可能没有听说过的有用的 Java 特性。这是我最近使用的功能的私人列表,或者是我在阅读有关 Java 的文章时偶然发现的。我不会关注语言方面,而是关注 API。
  1. 延迟队列【Delay Queue】
  如您所知,Java 中有许多类型的集合可用。但你听说了DelayQueue?吗?它是一种特定类型的 Java 集合,它允许我们根据元素的延迟时间对元素进行排序。老实说,这是一门非常有趣的课。尽管 DelayQueue? ****该类是 Java 集合的成员,但它属于 java.util.concurrent? 包。它实现了BlockingQueue接口。只有当元素的时间到期时,才能从队列中取出元素。
  为了使用它,首先在类里面需要实现接口中的getDelay?方法Delayed。它不必是一个类——你也可以使用 Java Record。
  public record DelayedEvent(long startTime, String msg) implements Delayed {
      public long getDelay(TimeUnit unit) {
          long diff = startTime - System.currentTimeMillis();
          return unit.convert(diff, TimeUnit.MILLISECONDS);
      }
      public int compareTo(Delayed o) {
          return (int) (this.startTime - ((DelayedEvent) o).startTime);
      }
  }
  假设我们想推迟元素10秒。我们只需要设置当前时间增加了10秒DelayedEvent类。
  final DelayQueue<DelayedEvent> delayQueue = new DelayQueue<>();
  final long timeFirst = System.currentTimeMillis() + 10000;
  delayQueue.offer(new DelayedEvent(timeFirst, "1"));
  log.info("Done");
  log.info(delayQueue.take().msg());
  上面可见代码的输出是什么?让我们来看看。
  2. 时间格式的天数【Period of Days in Time Format】
  Java 8 改进了很多时间处理 API。从这个版本的 Java 开始,在大多数情况下,可能不必使用任何额外的库,如 Joda Time。你能想象从 Java 16 开始,你甚至可以使用标准格式化程序来表示一天中的时段,例如“早上”或“下午”?有一种称为 B 的新格式模式。
  String s = DateTimeFormatter
    .ofPattern("B")
    .format(LocalDateTime.now());
  System.out.println(s);
  这是我的结果。当然结果取决于在一天中的时间。
  3. 印戳锁【StampedLock】
  Java Concurrent 是最有趣的 Java 包之一。同时,它也是开发人员鲜为人知的一种,尤其是当他们主要使用 Web 框架时。有多少人曾经在 Java 中使用过锁?锁定是一种比“同步”块更灵活的线程同步机制。从 Java 8 开始,您可以使用一种称为“StampedLock”的新型锁。 StampedLock? 是使用 ReadWriteLock 的替代方法。它允许对读取操作进行乐观锁定。此外,它比 ReentrantReadWriteLock 具有更好的性能。
  假设我们有两个线程。其中第一个更新余额,而第二个读取余额的当前值。为了更新余额,我们当然需要先读取它的当前值。我们在这里需要某种同步,假设第一个线程同时运行多次。第二个线程只是说明了如何使用乐观锁进行读取操作。
  StampedLock lock = new StampedLock();
  Balance b = new Balance(10000);
  Runnable w = () -> {
     long stamp = lock.writeLock();
     b.setAmount(b.getAmount() + 1000);
     System.out.println("Write: " + b.getAmount());
     lock.unlockWrite(stamp);
  };
  Runnable r = () -> {
     long stamp = lock.tryOptimisticRead();
     if (!lock.validate(stamp)) {
        stamp = lock.readLock();
        try {
           System.out.println("Read: " + b.getAmount());
        } finally {
           lock.unlockRead(stamp);
        }
     } else {
        System.out.println("Optimistic read fails");
     }
  };
  现在,让我们通过同时运行两个线程 50 次来测试它。它应该按预期工作 - 余额的最终值为 60000。
  ExecutorService executor = Executors.newFixedThreadPool(10);
  for (int i = 0; i < 50; i++) {
     executor.submit(w);
     executor.submit(r);
  }
  4. 并行累加器【Concurrent accumulators】
  锁并不是 Java Concurrent 包中唯一有趣的特性。另一种称为并发累加器。还有并发加法器,但它是一个非常相似的功能。 LongAccumulator?(也有DoubleAccumulator?)使用提供的函数更新值。它允许我们在许多场景中实现无锁算法。当多个线程更新一个公共值时,通常最好使用 AtomicLong。
  让我们看看它是如何工作的。为了创建它,您需要在构造函数中设置两个参数。第一个是用于计算累加结果的函数。通常,您会使用 sum 方法。第二个参数表示我们的累加器的初始值。
  现在,让我们使用初始值10000?创建LongAccumulator?,然后从多个线程调用accumulate()方法。最终结果是什么?
  LongAccumulator balance = new LongAccumulator(Long::sum, 10000L);
  Runnable w = () -> balance.accumulate(1000L);
  ExecutorService executor = Executors.newFixedThreadPool(50);
  for (int i = 0; i < 50; i++) {
     executor.submit(w);
  }
  executor.shutdown();
  if (executor.awaitTermination(1000L, TimeUnit.MILLISECONDS))
     System.out.println("Balance: " + balance.get());
  assert balance.get() == 60000L;
  5. 十六进制格式【Hex Format】
  有时我们需要在十六进制格式的字符串、字节或字符之间进行转换。从 Java 17 开始,您可以使用 HexFormat? 类。只需创建一个 HexFormat 实例,然后您就可以格式化例如输入 byte 到十六进制字符串的表。你也可以例如将输入的十六进制字符串解析为字节表,如下所示。
  HexFormat format = HexFormat.of();
  byte[] input = new byte[] {127, 0, -50, 105};
  String hex = format.formatHex(input);
  System.out.println(hex);
  byte[] output = format.parseHex(hex);
  assert Arrays.compare(input, output) == 0;
  6. 数组的二分法检索【Binary Search for Arrays】
  假设我们想在排序表中插入一个新元素。 Arrays.binarySearch()? 返回搜索键的索引(如果包含)。否则,它返回一个i插入点,我们可以使用它来计算新键的索引:-(insertion point)-1?。此外,binarySearch 方法是在 Java 中查找已排序数组中元素的最简单、最有效的方法。
  示例如下,我们有一个输入表,其中四个元素按升序排列。我们想在此表中插入数字“3”。这是我们如何计算插入点的索引的方法。
  int[] t = new int[] {1, 2, 4, 5};
  int x = Arrays.binarySearch(t, 3);
  assert ~x == 2;
  7. 位图【Bit Set】
  如果我们需要对位数组执行一些操作怎么办?你会为此使用``boolean[]?有一种更有效和内存效率更高的方法来实现它。它是 BitSet 类。 BitSet 类允许我们存储和操作位数组。与 ?boolean[] 相比,它消耗的内存少了 8 倍。我们可以对数组执行逻辑操作,例如?and, ?or, xor`。
  假设我们有两个输入位数组。我们想对它们执行 xor? 操作。事实上,只返回那些元素的操作,这些元素只插入一个数组,而不是另一个数组。为此,我们需要创建两个“BitSet”实例并在其中插入示例元素,如下所示。最后应该在 BitSet? 实例之一上调用 xor 方法。它将第二个 BitSet 实例作为参数。
  BitSet bs1 = new BitSet();
  bs1.set(0);
  bs1.set(2);
  bs1.set(4);
  System.out.println("bs1 : " + bs1);
  BitSet bs2 = new BitSet();
  bs2.set(1);
  bs2.set(2);
  bs2.set(3);
  System.out.println("bs2 : " + bs2);
  bs2.xor(bs1);
  System.out.println("xor: " + bs2);
  这是运行上面可见的代码后的结果。
  8. 移相器【Phaser】
  和这里的其他一些例子一样,它也是 Java Concurrent 包的元素。它被称为“移相器”。它与更知名的 CountDownLatch 非常相似。但是,它提供了一些附加功能。它允许我们设置在继续执行之前需要等待的动态线程数。使用“Phaser”,定义的线程数需要在屏障上等待,然后才能进入下一步执行。多亏了这一点,我们可以协调执行的多个阶段。
  在下面的示例中,我们设置了 50 个线程的屏障,以便在进入下一个执行阶段之前到达。然后我们创建一个线程,在 Phaser? 实例上调用方法 arriveAndAwaitAdvance()?。它阻塞线程,直到所有 50 个线程都不会到达屏障。然后它进入 phase-1? 并调用方法 arriveAndAwaitAdvance()。
  Phaser phaser = new Phaser(50);
  Runnable r = () -> {
     System.out.println("phase-0");
     phaser.arriveAndAwaitAdvance();
     System.out.println("phase-1");
     phaser.arriveAndAwaitAdvance();
     System.out.println("phase-2");
     phaser.arriveAndDeregister();
  };
  ExecutorService executor = Executors.newFixedThreadPool(50);
  for (int i = 0; i < 50; i++) {
     executor.submit(r);
  }
  这是执行上面可见的代码后的结果。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号