书接上一回,为了方便日后汇总序号也将从上一篇延续下去。
当前线程被锁定时调用Thread.sleep,则可能导致性能和可伸缩性问题,更有甚者可能会出现死锁的情况,这是因为持有锁的线程执行被冻结。
所以在这种情况下使用wait方法代替,以临时释放锁并允许其他线程运行。
Thread.sleep与wait的区别:
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu中其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。
请不要直接使用java.net.URL来进行操作了,我建议在实际需要访问资源时可以使用URI.toURL方法将URI转换为URL。
java.net.URL的equals和hashCode方法都可以触发名称服务(通常是DNS)查找来解析主机名或IP地址。
根据配置和网络状态,可能需要很长时间。 另一方面,URI不进行此类调用因此应该使用它,除非需要特定的URL功能。
曾经在一个编码不规范的项目中发现主表字段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之后再做比较。
会有人使用isLast方法来判断是否最后一行的吗?
在 Java 的 ResultSet 中,isLast() 方法用于判断当前 ResultSet 是否指向结果集的最后一行。虽然这个方法在某些情况下可以使用,但是不建议在实际开发中使用它,主要有以下两个原因:
不同的数据库实现中对 isLast() 方法的支持情况可能不同。有些数据库可能无法直接确定 ResultSet 的最后一行,因此对于这些数据库,isLast() 方法的实现可能需要遍历整个结果集。这会导致性能问题,因为遍历整个结果集需要大量的时间和资源。此外,由于不同数据库对 isLast() 方法的实现可能不同,因此在不同的数据库之间移植代码时可能会出现问题。
在某些情况下,如果结果集在检索期间发生了变化,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
}
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更有可能提高程序的性能。