有用的和不为人知的Java特性

2022/1/9 17:05:17

本文主要是介绍有用的和不为人知的Java特性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在日常java开发中,在jdk8以前 集合的操作比较麻烦,特别是对集合空的判断,需要写一些重复相似的代码去做判断,但是在jdk8以后,concurrent 包下有丰富的集合接口,简化了之前使用集合的复杂度.这里说一些有用的几个特性且容易被忽略的.

 

延迟队列

在开发中如果需要把元素加入到一个队列集合中,但是希望它能够延迟执行,那这里就推荐使用 DelayQueue 队列.这样在加入队列的时候设置这个元素和对象的过期时间就可以了,过期时间到了,就会从队列中出来.这是需要实现Delayed接口,重写掉getDelay和compareTo函数 代码如下:

    @Data
    public static class DelayedEvent implements Delayed {
        private long startTime;
        private String message;

        public DelayedEvent(long startTime, String message) {
            this.startTime = startTime;
            this.message = message;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            long diff = this.startTime - System.currentTimeMillis();
            return unit.convert(diff, TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return (int) (this.startTime - ((DelayedEvent) o).startTime);
        }
    }

 

调用代码如下,加入元素后,10S之后出对列:

final DelayQueue<DelayedEvent> delayQueue = new DelayQueue<>();
final long timeFirst = System.currentTimeMillis() + 10000;
delayQueue.offer(new DelayedEvent(timeFirst, "hello word"));
log.info("add done");
log.info(delayQueue.take().getMessage());

结果如下:

09:42:02.732 [main] INFO com.test.util.queue - add done
09:42:12.729 [main] INFO com.test.util.queue - hello word

 

时间格式日期

 

在jdk16以前,如果要根据当前时间判断是上午还是下午,那则需要自己实现函数,在函数里面根据一天不同时段做判断,在jdk14以后,可以用下面函数实现:

这样随着时间的变化,返回的时间提示也在动态变化.

String timeHit = DateTimeFormatter.ofPattern("B").format(LocalDateTime.now());
System.out.println(timeHit);

DateTimeFormatter 支持的字母列表如下:

结果返回如下:

 

 

并发累计器

 

又叫做并发加法器, 它允许我们在许多场景中实现一个无锁算法,java concurrent包下提供了LongAccumulator (也有 DoubleAccumulator)使用提供的函数更新值,

在多线程场景下,如果要更新一个int,long 类型的值,通常我们会用AtomicInteger,AtomicLong.给定初始值为10,然后50个线程并发调用,每次累计加1,这里我们直接用 Accumulator 结构实现:

LongAccumulator accumulator = new LongAccumulator(Long::sum, 10);
Runnable w = () -> accumulator.accumulate(1);
ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
  executor.submit(w);
}
executor.shutdown();
System.out.println("Balance: " + accumulator.get());

结果返回如下:

 

十六进制格式

 

在jdk8版本中,如果需要在十六进制、字符串、字节 类型相互转换时,转换的操作稍微复杂.在jdk 17版本中提供了HexFormat类来完成这之间的相互转换.代码如下:

HexFormat format = HexFormat.of();
byte[] input = new byte[] {100, 20, -10, 30};
String hex = format.formatHex(input);
System.out.println(hex);
byte[] output = format.parseHex(hex);
assert Arrays.compare(input, output) == 0;

 

 

数组的二分查找

 

在jdk1.8以前,在给定有序数组集合中,查找一个元素的位置,有则返回元素位置,没有返回-1,这需要手动实现二分查找算法,该算法每次把元素近似二分,然后查找.在 jdk1.8 以后提供了底层算法支持,

例如在给定有序集合中,查找元素x,如果x 存在则,返回元素的位置,元素不存在则返回插入该元素的位置索引,代码如下:

 int[] t = new int[]{10, 15, 20, 21};
 int idx = Arrays.binarySearch(t, 15);
 System.out.println("元素位置:" + idx);
 idx = Arrays.binarySearch(t, 100);
 System.out.println("插入元素的位置:" +~idx);

这里补充一下:

取反操作符(~)结论总结:
当n为正数时,~(n) = -(n+1)
当n为负数时,~(-n) = n - 1,忽略负号

结果如下:

 

 

Bit Set

 

如果我们需要对位数组进行操作,一般操作就是我们申明一个boolean 类型的数组,每一位用boolean表示true和fasle(0和1).有了bit set 类,我们就可以申明为 BItSet .该类允许我们存储和操作位的数组。与布尔值[]相比,它消耗的内存要少8倍,我们可以对数组执行逻辑操作,例如,和,或,xor (可以理解就是集合中的 并集 交集 差集),代码如下:

        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);
        bs2.and(bs1);
        System.out.println("and: " + bs2);

 

结果如下:

 

 

 

Phaser

 

phaser 是解决了分阶段的并发问题,与的 countdowlatch 非常相似。然而,它提供了一些额外的功能。它允许我们设置在继续执行之前需要等待的线程的动态数量。使用 Phaser 时,定义的线程数需要在屏障上等待,然后才能进入下一步的执行。正因为如此,我们可以协调多个执行阶段.定义3个线程,这3个线程共同有3个阶段,第一个阶段执行完之后,再执行下一个阶段,代码如下:

Phaser phaser = new Phaser(3);
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(3);
for (int i = 0; i < 3; i++) {
    executor.submit(r);
}

 

结果如下:

 

Stamped Lock

 

javaconcurrent 是最有趣的java包之一,该包提供了很多多线程下并发使用的工具和集合.该锁的核心思想在于,在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。这种模式也就是典型的无锁编程思想,和CAS自旋的思想一样。这种操作方式决定了StampedLock在读线程非常多而写线程非常少的场景下非常适用,同时还避免了写饥饿情况的发生.是ReadWriteLock 的替代品,它允许对读操作进行乐观锁定。而且,它的性能比 ReentrantReadWriteLock 更好。 下面的例子是一个多个线程并发执行,其中一个线程是对一个数的自增操作,另外一个线程则是读出最新写入后的结果,其实根据所的特性,读采取乐观锁,理论上不会阻塞写操作.

public class Number {
    private int acc;

    public Number(int amount) {
        this.acc = amount;
    }
}
StampedLock lock = new StampedLock();
Number b = new Number(10);
Runnable w = () -> {
    long stamp = lock.writeLock();
    b.setAcc(b.getAcc() + 1);
    System.out.println("acc: " + b.getAcc());
    lock.unlockWrite(stamp);
};
Runnable r = () -> {
    long stamp = lock.tryOptimisticRead();
    if (!lock.validate(stamp)) {
        stamp = lock.readLock();
        try {
            System.out.println("read acc: " + b.getAcc());
        } finally {
            lock.unlockRead(stamp);
        }
    } else {
        System.out.println("Optimistic read fails");
    }
};

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
    executor.submit(w);
    executor.submit(r);
}

下面的直接结果在没台机器上执行的过程可能不太一样,这取决于cpu的速速,但是最终结果一定是20.

执行结果如下:

 



这篇关于有用的和不为人知的Java特性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程