网站首页 > java教程 正文
一、ConcurrentHashMap 陷阱:
以下代码使用Watcher来记录特定Key的调用次数,考虑到线程安全,使用了ConcurrentHashMap, 这段代码真的能实现线程安全吗?
public class Watcher
{
private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
public void add(String key)
{
Integer value = map.get(key);
if(value == null)
{
map.put(key, 1);
}
else
{
map.put(key, value + 1);
}
}
}
答案:这个代码片段是线程非安全的,比如 A、B 两个线程同时调用 add("newKey")方法,如下表所示。
T4 时刻时,线程 B 覆盖了线程 A 在 T3 时刻的值,系统调用了 add("newKey")两次,但“newKey”的值为 1。
以下是一个正确的代码片段:
private ConcurrentHashMap<String, AtomicInteger> countMap
= new ConcurrentHashMap<String, AtomicInteger> ();
public void add(String key){
AtomicInteger count = countMap.computeIfAbsent(key k->new AtomicInteger());
//自增
count.incrementAndGet();
}
二、字符串搜索:
以下代码的作用是搜索“{”出现的位置,是否有性能更好的替代方法?
String str = ...;
int index = str.indexOf("{");
答案:考虑到只搜索单个 char,String 还提供了根据 char 来搜索的 API。高效代码如下:
int index = str.indexOf('{'};
类似的还有 String.replace(char,char)、StringBuilder.append(char),比如拼接一个 JSON,可以使用 append(char)方法:
StringBuilder sb = new StringBuilder();
sb.append('{')...append(':')...apend('}')
三、I/O输出:
以下代码输出 HTML,从性能的角度考虑有没有调整的地方?
try {
writer.write("<html>");
writer.write("<body>");
writer.write("<h1>"+topic.getTitle+"</h1>");
writer.write("</body>");
writer.write("</html>");
} catch (IOException e) {
throws e;
}
答案:可以考虑合并输出,减小 write 方法的调用次数。Writer 类内部的一些操作可能会影 响性能,比如,如果 Writer 是一个 StringWriter 实例,那么 write 方法内部还会有扩容操作。如果是一个 Servlet 的 Writer,则可能会检测是否需要输出到客户端。合并调用有助于提高性能:
writer.write("<html><body>");
writer.write("<h1>"+topic.getTitle+"</h1>");
writer.write("</body></html>");
另一方面,可以考虑使用模板技术,比如模板引擎。编写一个模板:
<html>
<body>
<h1>${topic.title}</h1>
</body>
</html>
模板使得输出内容更容易维护。同时模板引擎也非常高效,性能几乎跟采用 StringBuilder拼接字符串一样高。大部分模板引擎都会合并静态文本,但很多 Web 服务器的 JSP 在反编译的时候却没有合并静态文本。
四、字符串拼接:
以下代码需要把对象的地址列表拼接成一个字符串,如何提高性能?
public String buildProvince(List<Org> orgs){
StringBuilder sb = new StringBuilder();
for(Org org:orgs){
if(sb.length()!=0){
sb.append(",")
}
sb.append(org.getProvinceId())
}
return sb.toString();
}
答案:可以考虑预先指定一个 StringBuilder 的容量,如果 provinceId 的长度不超过 3 位,那么可以估算出 StringBuilder 的容量。使用逗号分隔,可以将 append(",")改成 append(',')。最后,也可以预先将 provinceId 对应的 int 转为字符串以避免 int 转 String 的性能消耗。调整后的代码如下:
//建立一个缓存
static int cacheSize = 1024;
static String[] caches = new String[cacheSize];
static {
for(int i=0;i<cacheSize;i++){
caches[i] = String.valueOf(i);
}
}
public String buildProvince(List<Org> orgs){
if(orgs.isEmpty()){
return "";
}
StringBuilder sb = new StringBuilder(orgs.size()*4);
for(Org org:orgs){
if(sb.getLength!=0){
sb.append(',')
}
sb.append(int2String(org.getProvinceId())
}
return sb.toString();
}
public static String int2String(int data) {
if (data < cacheSize) {
return caches[data];
} else {
return String.valueOf(data);
}
}
五、方法的入参和出参:
以下这段代码是否有改善的地方?
public Map<String, Object> queryPathInfo(Map<String, Object> param) {
return Dao.selectOne(basepath + "selRedeemInfo", param);
}
类似的,有的方法使用 JsonObject 作为输入参数或返回值:
JsonObject funcName(Context req, JsonObject params)
答案:无论使用 Map,还是使用 JsonObject,作为入参或返回值,都非常难以阅读。除非再次阅读方法的完整实现过程,才会知道 Map 或 JsonObject 到底包含什么样的数据。建议使用具体的对象代替这种理解较为抽象的类:
public PathInfo queryPathInfo(PathQuery param) {
//大部分 Dao 框架都支持映射数据库结果集到一个个具体的类上
return Dao.selectOne(basepath + "selRedeemInfo", param,PathInfo.class);
}
六、RPC 调用定义的返回值:
一个 RPC 框架的返回值的定义如下,使用 Object 数组是否可行?
public Object[] rpcCall(Request request ){
try{
Object ret = proxy.query(request);
return new Object[]{true,ret};
}catch(Exception ex){
return new Object[]{false};
}
}
类似的,一个 JPA 的查询结果如下:
@Query("select t.itemtype, count(t.id) from TxxItem t where t.parentCode like ?1
and t.state='1' group by t.itemtype ")
public List<Object[]> getFaciCount(String parentCode );
答案:使用对象,而不要使用数组。
public CallResult rpcCall(Request request){}
class CallResult{
boolean success;
Object ret;
}
JPA 的查询结果返回 List,这也使得维护者不得不阅读 SQL 才知道返回的值,而且对于数字类型,count 返回的结果根据驱动或数据库的不同而不同,有的返回 BigDecimal,有的返回BigInteger,这种写法带来了隐患。
七、Integer 的使用:
以下代码判断两个整数是否相等,代码是否有问题?
public boolean isEqual(Integer a,Integer b){
return a==b;
}
下面这段代码的最终结果是 null 吗?
Double a = 1D;
Double b = 2D;
Double c = null;
Double d = isSuccess() ? a*b : c;
答案:这里涉及装箱和拆箱,第一个例子使用“==”比较两个对象是否是同一个,在数字-128 到 127 之间,因为这个范围的装箱都使用了缓存,因此可以这么用,如果数字不在这个范围内,则应该用 equals 进行比较。
应该改成如下代码:
public boolean isEqual(Integer a,Integer b){
if(a==b){
return true;
}
if(a==null||b==null){
return false;
}
return a.equals(b);
}
在三元表达式中,如果 isSuccess()返回 false,则代码片段会抛出空指针。字节码如下:
INVOKESTATIC com/ibeetl/code/style/Test1.isSuccess ()Z
IFEQ L0
//比较,如果为 true
ALOAD 1 //变量 a
INVOKEVIRTUAL java/lang/Double.doubleValue ()D
ALOAD //变量 b
INVOKEVIRTUAL java/lang/Double.doubleValue ()D
//a*b
DMUL
GOTO L1
L0
//false:
ALOAD 3 //变量 c,拆箱导致空指针
INVOKEVIRTUAL java/lang/Double.doubleValue ()D
L1
INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
ASTORE 4
RETURN
可以看到,在三元表达式中,由于 a*b 有拆箱,因此变量 c 也会拆箱,导致空指针。可以改成以下代码:
Double d = null;
if(isSuccess()){
d = a*b;
}else{
d = c;
}
内容摘自《高性能Java系统权威指南》第八章
本书特点:
内容上,总结作者从事Java开发20年来在头部IT企业的高并发系统经历的真实案例,极具参考意义和可读性。
对于程序员和架构师而言,Java 系统的性能优化是一个超常规的挑战。这是因为 Java 语言和 Java 运行平台,以及 Java 生态的复杂性决定了 Java 系统的性能优化不再是简单的升级配置或者简单的 “空间换时间”的技术实现,这涉及 Java 的各种知识点。
本书从高性能、易维护、代码增强以及在微服务系统中编写Java代码的角度来描述如何实现高性能Java系统,结合真实案例,让读者能够快速上手实战。
风格上,本书的风格偏实战,读者可以下载书中的示例代码并运行测试。读者可以从任意一章开始阅读,掌握性能优化知识为公司的系统所用。
本书适合:
中高级程序员和架构师;
以及有志从事基础技术研发、开源工具研发的极客阅读;
也可以作为 Java 笔试和面试的参考书。
猜你喜欢
- 2024-11-07 有了这50套Java毕设项目(源码 案例),offer拿到手软,无偿分享
- 2024-11-07 练手的70个超火python小项目,小编建议收藏哦,送你们玩去吧
- 2024-11-07 Java 实例(java实例变量和类变量的区别)
- 2024-11-07 Java开发实例大全(PDF):603个典型事例及源码分析和24个应用方向
- 2024-11-07 JAVA项目案例详解(带代码)(java 案例)
你 发表评论:
欢迎- 05-02Go 中的 channel 与 Java BlockingQueue 的本质区别
- 05-02处理线上RabbitMQ队列阻塞(rabbitmq队列状态)
- 05-02实现延迟队列,这些你知道吗?(延迟队列 kafka)
- 05-02学无止境:AQS阻塞队列和条件队列是如何使用的?
- 05-02京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
- 05-02阻塞队列ArrayBlockingQueue的实现原理浅析
- 05-02高性能队列:Java Concurrent包中的BlockingQueue
- 05-02不允许还有Java程序员不了解BlockingQueue阻塞队列的实现原理
- 最近发表
-
- Go 中的 channel 与 Java BlockingQueue 的本质区别
- 处理线上RabbitMQ队列阻塞(rabbitmq队列状态)
- 实现延迟队列,这些你知道吗?(延迟队列 kafka)
- 学无止境:AQS阻塞队列和条件队列是如何使用的?
- 京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
- 阻塞队列ArrayBlockingQueue的实现原理浅析
- 高性能队列:Java Concurrent包中的BlockingQueue
- 不允许还有Java程序员不了解BlockingQueue阻塞队列的实现原理
- JAVA并发之BlockingQueue(阻塞队列)
- dify案例分享-API文档生成接口代码
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)