【Java】知识点汇总(2)

书接上一回,为了方便日后汇总序号也将从上一篇延续下去。

7. for与foreach使用比较

结论
  • for适用于下标循环;
  • foreach采用的是迭代器循环;
  • 在ArrayList的情况下for优势明显;
  • 在LinkedArrayList的情况下foreach优势明显;
分析
  • 由于for采用的是下标的循环方式,因此属于随机读取,我可以通过下标读取任意下标对应的值,但是这种方式也存在一些问题,譬如通过循环删除集合中的元素时就会抛出下标越界的问题。
  • 而foreach采用的是顺序读取的数据结构格式,可以通过iterater(迭代器)来遍历集合,这个时候由于没有下标什么事了,因此也不会影响到集合遍历删除,但是这种方式没有办法删除指定的对象。

8. 当前线程被锁定时用wait代替sleep

结论

当前线程被锁定时调用Thread.sleep,则可能导致性能和可伸缩性问题,更有甚者可能会出现死锁的情况,这是因为持有锁的线程执行被冻结。

所以在这种情况下使用wait方法代替,以临时释放锁并允许其他线程运行。

分析

Thread.sleep与wait的区别:

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

sleep()方法导致了程序暂停执行指定的时间,让出cpu中其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。

9. 不要直接使用URL类操作

结论

请不要直接使用java.net.URL来进行操作了,我建议在实际需要访问资源时可以使用URI.toURL方法将URI转换为URL。

分析

java.net.URL的equals和hashCode方法都可以触发名称服务(通常是DNS)查找来解析主机名或IP地址。
根据配置和网络状态,可能需要很长时间。 另一方面,URI不进行此类调用因此应该使用它,除非需要特定的URL功能。

10. Long和String的比较判断

曾经在一个编码不规范的项目中发现主表字段id为bigint类型,子表中对应字段为逻辑外键且数据类型为varchar类型的存在。由于数据库没有强制外键关联,因此没有在数据库层面进行校验。从而导致了在Java实体中主表实体id的数据类型为Long,而子表实体中该id对应的数据类型为String。
在Java中Long类型是存在equals判断的,有很多小伙伴在修改代码时只关注字段名称而忽略数据类型时,就会出现以下写法:

// 伪代码
if(Long.equals(String)){
    
    
    ...
}

不幸的是在IDE中这种写法是不会抛出错误提示的,而实际上在编译器层面上也是可以编译通过的。
但是在实际操作上数据在判断时会判false,因为equals方法是不会类型转换的,因此两个数据类型不同的情况下是无法进行判断的。
万一真的不幸在工作过程中遇到这种情况,不想改祖传代码的情况下最低成本的修改方式是:

if(String.valueOf(Long).equals(String)){
    
    
    ...
}

先用String.valueOf转换成String再做比较。反之若要判断大小,则将String转换为Long之后再做比较。

11. ResultSet循环取值不能用isLast方法

会有人使用isLast方法来判断是否最后一行的吗?
在 Java 的 ResultSet 中,isLast() 方法用于判断当前 ResultSet 是否指向结果集的最后一行。虽然这个方法在某些情况下可以使用,但是不建议在实际开发中使用它,主要有以下两个原因:

  1. 不同数据库的实现不同

不同的数据库实现中对 isLast() 方法的支持情况可能不同。有些数据库可能无法直接确定 ResultSet 的最后一行,因此对于这些数据库,isLast() 方法的实现可能需要遍历整个结果集。这会导致性能问题,因为遍历整个结果集需要大量的时间和资源。此外,由于不同数据库对 isLast() 方法的实现可能不同,因此在不同的数据库之间移植代码时可能会出现问题。

  1. 结果集的变化

在某些情况下,如果结果集在检索期间发生了变化,isLast() 方法的结果可能会出现问题。例如,如果在检索期间插入或删除了行,则结果集中的行数会发生变化。这可能会导致 isLast() 方法的结果不正确,因为结果集中的最后一行可能已经不是原来的最后一行了。
因此建议大家不要用isLast方法了,用next来代替吧:

stmt.executeQuery("SELECT name, address FROM PERSON"); 
ResultSet rs = stmt.getResultSet(); 
while (! rs.isLast()) {
    
     // Noncompliant 
// process row 
} 

改为

ResultSet rs = stmt.executeQuery("SELECT name, address FROM PERSON"); 
while (! rs.next()) {
    
     
// process row 
}

12. 关于Long的原子性操作推荐使用LongAdder

LongAdder和AtomicLong都是用于实现原子更新长整型数据的类。LongAdder和AtomicLong的主要区别在于它们对高并发环境下的性能表现不同。

AtomicLong的性能在低并发环境下非常好,但在高并发环境下,因为所有线程都需要竞争同一个内部变量的更新,因此可能会导致性能瓶颈。

相比之下,LongAdder的性能在高并发环境下更好。

LongAdder的内部实现是基于分段锁的思想,它将内部状态分成了多个变量,每个变量独立地累加值。这些变量被称为“单元”,每个单元都有一个独立的计数器。当多个线程同时更新时,它们可以分别更新不同的单元,从而减少了线程之间的竞争。

LongAdder中有两个关键的成员变量:base和cells。base是一个长整型变量,表示所有单元的总和。cells是一个数组,每个元素都是一个单元,表示独立的计数器。在初始化时,LongAdder只有一个单元,即base,它的值为0。当有多个线程更新计数器时,LongAdder会根据需要动态地增加单元的数量。

更新单元时,LongAdder会使用分段锁来避免竞争。分段锁将内部状态分成了多个部分,每个部分都有一个独立的锁,因此多个线程可以同时更新不同的部分,从而提高了并发性能。

在更新单元时,LongAdder使用ThreadLocalRandom来选择要更新的单元,以减少线程之间的竞争。具体来说,它使用线程本地的随机数生成器来选择要更新的单元,而不是简单地采用轮询或其他策略。

在获取当前值时,LongAdder将所有单元的值相加,得到总和。如果存在多个单元,则需要获取它们的值并加上base的值。由于单元数量可能会动态变化,因此在计算总和时需要特殊处理。

LongAdder的内部实现基于分段锁和ThreadLocalRandom,它将内部状态分成了多个部分,每个部分有一个独立的计数器和锁,在高并发情况下,这种分离状态的方式可以更好地利用多核处理器的性能。

坦白说,如果你需要在高并发环境下进行原子长整型的累加操作,那么使用LongAdder比AtomicLong更有可能提高程序的性能。

猜你喜欢

转载自blog.csdn.net/kida_yuan/article/details/129488749