提升 Java 应用程序的十个优化技巧

上一篇 / 下一篇  2023-03-10 16:15:27

  程序性能优化是一个复杂的话题。往往需要结合具体场景进行性能分析,找出瓶颈提出优化建议。但是,假设我们平时很少关注细节的性能,那么这种情况下,优化这些细节所带来的收益也是相当可观的。接下来,我们就来说说Java代码细节优化的一些小技巧。
  复杂的字符串连接操作使用 StringBuilder
  职业生涯早期,在做字符串连接操作的时候,肯定会这么写:String a=c+e+d,这个Java语法糖对于开发者来说太方便了。但是如果你在循环中使用“+”,那就得小心了。
  String a=null;
  for(int i=0;i<1000;i++) {
    a=a+i;
  }
  我们都知道String 是不可变的,因此循环中对 string 的每一次赋值都会在堆内存中创建一个新的 String 对象。在一个循环体中,反复创建多个无用的对象,不仅会占用内存空间,还会影响GC时间。所以说,如果在循环中遇到字符串拼接,就使用 StringBuilder 而不是“+”。
  使用 ThreadPoolExecutor 避免手动创建线程
  许多初学者喜欢在编写代码时创建线程,这是一种危险的做法。
  如果这个线程的创建需要处理大量的请求,很可能导致你的程序频繁的创建和销毁线程,频繁的切换线程上下文,浪费CPU资源,甚至会耗尽内存。
  因此,建议使用ThreadPoolExecutor,并配置合适的核心线程数和最大线程数。
  为集合预分配适当的容量
  我们都知道 ArrayList,HashMap 和 ConcurrentHashMap 等集合类是可以自动扩容的,但是这种自动扩容涉及到底层数组的复制和迁移。如果扩容频繁,肯定会影响程序的性能。所以如果你能估计出大概的容量,请直接配置初始值。
  使用枚举而不是常量类
  很多人特别喜欢在项目中创建一个常量类,如下:
  public class Constant {
      public static final String TOKEN_HEADER = "x-request-token";
      public static final Integer CODE_SUCCESS = 0;
      public static final Integer CODE_REQUEST_FAILED = 1;
      
      public static final Integer CODE_REQUEST_RUNNING = 2;
  }
  为什么不用枚举呢?Enum 有强制的类型验证。同时,使用枚举类的性能更高。并且使用 enum 还有更大的优势,它可以与策略模式一起使用来提高程序的可扩展性。例如:
  public enum FileType {
      EXCEL(".xlsx"){
          @Override
          public void download(String path) {
                  //do download excel file logic
          }
      }, CSV(".csv") {
          @Override
          public void download(String path) {
            //do download csv file logic
          }
      };
      private String suffix;
      FileType(String suffix) {
          this.suffix = suffix;
      }
      public String getSuffix() {
          return suffix;
      }
      public abstract void download(String path);
  }
  如代码所示,你可以根据需要动态选择一种策略来下载文件,直接调用FileType.EXCEL.download(),无需关心代码细节。
  使用 NIO 代替传统 IO
  传统的 IO 已经过时了。强烈推荐使用 NIO 代替传统的 IO。因为传统IO采用阻塞IO模型,请求数据后,线程从数据准备到数据可读都是阻塞的。
  而且,传统IO如果要往网卡写数据,需要先把数据写到堆内存,然后再把数据拷贝到堆外的一块内存,再从用户态拷贝数据到内核状态缓冲区。最后CPU通知DMA将数据写入网卡,一共经历了3次拷贝。NiO不仅采用了multiplex IO模型,还可以使用direct memory来减少数据拷贝次数,从而提高性能。
  使用移位操作
  如果你看过一些JDK的源代码,比如HashMap,你会发现代码中有很多移位操作。因为JDK是比较底层的代码,对性能的追求也是极致的。在我们日常的编码中,可以用移位运算来代替一些乘除运算,比如a >> 1 代替 a / 2,a * 16 代替 a << 4。
  这个技巧也能在一定程度上提高性能,但是如果你不擅长,那就不要强求,因为当代计算机的性能已经非常强大了,没必要为了一个程序而牺牲代码的可读性。
  尝试使用单例模式
  如果我们设计一个不需要考虑线程安全的类,请用单例模式来使用这个类,这样可以节省内存。幸运的是,对于我们使用的spring框架,Java bean默认是单例的。
  降低锁粒度
  假设我们有一个共享文档编辑功能,用户会同时编辑共享文档。为了保证文件的正确性,我们需要使用线程安全synchronized来保证。很多初学者可能会这样写。
  public class Test{
      private Object lock = new Object();
      
      public void write(String username, String fileName) {
          synchronized(lock) {
              //do something
          }
      }
  }
  如果采用上述方式,只有一个线程可以进入同步代码块执行,其他线程只能挂起等待,即使这些线程可能写入不同的文件。我们可以通过降低锁粒度来提高性能。
  public class Test{
      
      public void write(String username, String fileName) {
          synchronized(fileName.intern()) {
              //do something
          }
      }
  }
  不要随意使用静态变量
  如果你熟悉JVM基础知识,那么就会知道如果一个对象被定义为静态变量,这个变量的引用就不容易被垃圾回收器回收。
  public class Test{
      public static A a = new A();
  }
  静态变量“a”的生命周期与测试类相同。只要测试类型没有被卸载,“a”的引用对象就会驻留在内存中,直到程序终止。
  使用基本数据类型
  在应用程序中使用基本数据类型来减少内存消耗并提高程序性能。如果可以使用 int,请不要使用其 Integer 包装类型,使用double 而不是 Double。
  基本数据类型的包装类实例存放在堆内存中,每次使用都会在堆内存中创建一个。如果使用基本数据类型,数据存放在栈帧中,栈的访问速度可比堆快很多。

TAG: 软件开发 Java java

 

评分:0

我来说两句

Open Toolbar