有用的和不为人知的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.

执行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当谈到C++编译器的小细节时,有一些不为人知的事实可以讨论。这些细节可能因编译器而异,以下是一些常见的例子: 1. 内联函数:内联函数是在调用点展开的函数。编译器会尝试内联短小的函数,以减少函数调用的开销。然而,并非所有的内联请求都会被编译器接受,例如递归函数、虚函数、函数体过大等情况。 2. 名称修饰(Name Mangling):C++编译器在编译过程中会对函数和变量的名称进行修饰,以支持函数重载和命名空间。这样做是为了在链接过程中能够正确地识别不同的符号。 3. 静态断言(Static Assertion):C++11引入了静态断言,它允许在编译时对表达式进行断言检查。如果断言失败,则会在编译时引发错误,而不是在运行时。 4. 注释处理:注释在编译过程中被完全忽略。这意味着,即使注释中包含有助于理解代码的信息,也不会影响编译结果。 5. 隐式类型转换:C++编译器在某些情况下会自动执行隐式类型转换,例如将整数类型提升为浮点类型,或者将派生类指针转换为基类指针。这些转换可能会导致意外的结果,因此需要谨慎使用。 6. 内存优化:编译器会对代码进行优化,以减少内存的使用。例如,它可以通过将多个变量存储在相同的内存位置上来减少内存占用,或者通过使用寄存器来提高变量访问的效率。 这些只是一些C++编译器的小细节,深入了解编译器的工作原理和各种编译器的行为将有助于编写更高效、可靠的代码。请注意,不同的编译器可能具有不同的行为和特性

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值