Week 10
week 10
高级篇
性能优化
减少锁粒度
在多线程编程中,如果多个线程需要访问共享资源,通常需要使用锁来保证资源的安全访问。但是,如果锁的粒度太粗,会导致线程之间的竞争过于激烈,从而影响程序的性能。
以下是使用减少锁粒度进行性能优化的主要方式:
- 细粒度锁:将共享资源的访问权限限制在一个尽可能小的范围内,从而减少多个线程同时访问共享资源的概率。例如,可以将一个共享变量拆分成多个独立的变量,并分别上锁,从而减少锁的粒度。
- 分段锁:将共享资源分成多个段,并对每个段分别上锁。这种方式可以减少多个线程同时访问同一份资源的概率,从而降低锁的竞争。
- 动态锁:根据实际情况动态地调整锁的粒度。例如,在某些情况下,可以将多个操作组合成一个原子操作,从而减少锁的使用。
- 无锁算法:使用无锁算法来避免使用锁。无锁算法通常使用乐观锁、CAS操作等技术来实现并发控制,从而避免使用锁带来的开销。
需要注意的是,减少锁粒度虽然可以提高程序的并发性能,但是也可能会增加程序的复杂性和代码量。因此,在进行性能优化时,需要根据实际情况进行适当的权衡和选择。同时,还需要注意避免过度优化导致代码可读性和维护性下降的问题。
数据压缩
可以减少数据的存储空间和传输时间,从而提高程序的性能。在某些情况下,数据压缩还可以减少CPU的计算量,从而提高程序的响应速度。
以下是使用数据压缩进行性能优化的主要方式:
- 压缩数据存储:将数据按照一定的算法进行压缩,然后存储到磁盘或内存中。这样可以在不损失数据精度的情况下减少数据的存储空间,从而减少存储设备的成本和IO操作次数。
- 压缩数据传输:在数据传输过程中,使用压缩算法对数据进行压缩,然后进行传输。这样可以在不损失数据精度的情况下减少数据的传输时间,从而提高程序的响应速度。
- 压缩算法选择:根据实际情况选择合适的压缩算法。不同的压缩算法有着不同的压缩比和解压速度,因此需要根据实际需求进行选择。
- 压缩与解压优化:对于需要频繁进行压缩和解压操作的数据,需要优化压缩和解压算法,以提高程序的性能。例如,可以使用缓存技术来减少重复压缩和解压的开销,或者使用多线程技术来并行处理压缩和解压操作。
需要注意的是,数据压缩虽然可以提高程序的性能,但是也可能会增加程序的复杂性和代码量。因此,在进行性能优化时,需要根据实际情况进行适当的权衡和选择。同时,还需要注意避免过度优化导致代码可读性和维护性下降的问题。
结果缓存
可以避免重复计算,从而提高程序的性能。在某些情况下,结果缓存可以避免CPU的计算量,从而提高程序的响应速度。
以下是使用结果缓存进行性能优化的主要方式:
- 内存缓存:将计算结果存储在内存中,当需要再次使用时,直接从内存中获取。这样可以避免重复计算,从而提高程序的性能。
- 磁盘缓存:将计算结果存储在磁盘上,当需要再次使用时,从磁盘中读取。这种方式适用于不经常修改的数据,可以避免重复计算,提高程序的性能。
- 缓存失效机制:设置缓存的失效时间,当缓存中的数据过期时,自动删除并重新计算。这样可以保证缓存数据的准确性,避免因为数据过期导致的错误。
- 缓存查询优化:对于频繁修改的数据,需要优化缓存查询算法,以提高程序的性能。例如,可以使用布隆过滤器等技术来快速判断数据是否存在,或者使用分布式缓存系统来分担负载。
需要注意的是,结果缓存虽然可以提高程序的性能,但是也可能会增加程序的复杂性和代码量。因此,在进行性能优化时,需要根据实际情况进行适当的权衡和选择。同时,还需要注意避免过度优化导致代码可读性和维护性下降的问题。
Stream并行流
利用多核CPU的优势,提高程序的并行处理能力。在Java 8之后,Stream API提供了并行流和串行流两种方式,其中并行流可以利用多核CPU的优势,加快数据处理速度。
以下是使用Stream并行流进行性能优化的主要方式:
- 并行流转换:将Stream转换为并行流,然后进行数据处理。这样可以利用多核CPU的优势,加快数据处理速度。但是需要注意,并行流并不一定适用于所有情况,需要根据实际情况进行选择。
- 并行操作:在并行流中使用并行操作,如
parallelFilter()
、parallelMap()
等,可以同时处理多个数据,提高程序的并行处理能力。 - 线程池设置:在使用并行流时,可以设置线程池的大小,以充分利用多核CPU的优势。线程池太小或太大都会影响程序的性能,因此需要根据实际情况进行设置。
- 数据分布均匀:在使用并行流时,需要保证数据分布均匀,避免出现数据倾斜的情况。如果数据分布不均匀,会导致部分线程处理过多数据,而其他线程处理数据较少,从而影响程序的性能。
- 关闭流操作:在使用并行流时,需要关闭流操作,以释放资源。如果不关闭流操作,会导致资源泄漏和其他问题。
需要注意的是,使用并行流虽然可以提高程序的并行处理能力,但是也需要注意线程安全和数据一致性问题。同时,也需要根据实际情况进行适当的权衡和选择,避免过度优化导致代码可读性和维护性下降的问题。
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
public class StreamParallelExample {
public static void main(String[] args) {
int size = 10000; // 数据量大小
List<Integer> numbers = new ArrayList<>(); // 存储随机数的列表
// 生成随机数列表
for (int i = 0; i < size; i++) {
numbers.add(new Random().nextInt(100)); // 生成0~99之间的随机数
}
// 使用Stream并行流进行性能优化
long startTime = System.currentTimeMillis();
List<Integer> result = numbers.parallelStream() // 将列表转换为并行流
.filter(num -> num > 50) // 使用并行过滤操作,筛选出大于50的数
.map(num -> num * 2) // 使用并行映射操作,将每个数乘以2
.collect(Collectors.toList()); // 将结果收集到新的列表中
long endTime = System.currentTimeMillis();
System.out.println("处理结果:" + result); // 输出处理结果
System.out.println("处理时间:" + (endTime - startTime) + "ms"); // 输出处理时间
}
}
在上述代码中,我们首先生成了一个包含10000个随机数的列表,然后使用parallelStream()
方法将其转换为并行流,并依次进行了过滤和映射操作,最后将结果收集到新的列表中。由于使用了并行流,这些操作可以并行执行,从而提高了程序的性能。
GC调优
Java GC(Garbage Collection,垃圾收集)调优是指通过调整Java虚拟机(JVM)的垃圾收集器参数,以优化程序的性能和资源利用率。以下是一些常见的Java GC调优建议:
- 了解GC机制:了解Java GC的工作原理和各种垃圾收集器的特点,以便选择适合自己应用程序的垃圾收集器。
- 调整堆大小:根据应用程序的需求和系统资源,调整堆的大小。如果堆过大,会导致内存浪费和频繁的GC;如果堆过小,会导致内存不足和频繁的OutOfMemoryError。
- 选择合适的垃圾收集器:根据应用程序的特点和需求,选择适合的垃圾收集器。例如,对于CPU密集型应用程序,可以选择Concurrent Mark Sweep(CMS)收集器;对于内存敏感的应用程序,可以选择Serial收集器。
- 设置垃圾收集日志:开启垃圾收集日志,以便监控和诊断GC问题。可以通过在启动JVM时添加相关参数来启用垃圾收集日志。
- 避免内存泄漏:确保程序中没有内存泄漏。内存泄漏会导致内存占用不断增加,最终导致OutOfMemoryError。
- 合理使用finalize方法:如果需要释放对象持有的资源,可以使用finalize方法。但是,必须注意finalize方法的执行时机和执行次数,以避免影响程序性能。
- 使用WeakReference和SoftReference:对于不需要长时间持有的对象,可以使用WeakReference或SoftReference代替强引用。这样可以让垃圾收集器在合适的时候释放对象占用的内存。
- 避免不必要的对象创建:过多的对象创建会增加GC的负担,因此应该尽量避免不必要的对象创建。
- 使用并发集合:如果程序中需要使用集合类,应该优先考虑使用并发集合,以避免在多线程环境下出现竞争条件和死锁等问题。
- 监控和诊断:使用工具监控和诊断JVM的性能和GC情况,以便及时发现问题并进行调优。常见的工具包括JConsole、VisualVM和GCViewer等。
总之,Java GC调优是一个复杂的过程,需要根据应用程序的特点和需求进行适当的调整。在进行GC调优时,应该先了解GC机制和各种垃圾收集器的特点,然后根据实际情况选择合适的参数进行调整,并监控和诊断程序的性能和GC情况,以便及时发现问题并进行调优。
JVM内存分配调优
JVM内存分配调优主要是指根据应用程序的需求和系统资源,合理地分配和调整JVM的内存空间,以提高程序的性能和资源利用率。以下是一些常见的JVM内存分配调优建议:
- 了解内存模型:了解JVM的内存模型和各种内存区域的特点,以便合理地分配内存空间。
- 调整堆大小:根据应用程序的需求和系统资源,调整堆的大小。如果堆过大,会导致内存浪费和频繁的GC;如果堆过小,会导致内存不足和频繁的OutOfMemoryError。
- 选择合适的垃圾收集器:根据应用程序的特点和需求,选择适合的垃圾收集器。例如,对于CPU密集型应用程序,可以选择Concurrent Mark Sweep(CMS)收集器;对于内存敏感的应用程序,可以选择Serial收集器。
- 设置堆参数:根据应用程序的需求和系统资源,设置堆的相关参数,如初始堆大小、最大堆大小、堆增长方式等。
- 调整线程栈大小:根据应用程序的线程数量和线程栈的使用情况,调整线程栈的大小。如果线程栈过小,会导致线程切换频繁和性能下降;如果线程栈过大,会导致内存浪费和频繁的OutOfMemoryError。
- 禁用无用类加载器:如果应用程序中不需要使用自定义类加载器,应该禁用自定义类加载器,以提高JVM的启动速度和性能。(java -XX:+DisableExplicitGC -jar xxx.jar)
- 使用压缩指针:如果应用程序中需要使用大量对象,而且对象的大小小于32字节,可以使用压缩指针来节省内存空间。(java -XX:+UseCompressedOops -jar xx.jar)通过使用压缩指针,可以减少每个对象的内存占用,并提高内存的使用效率。
- 使用JVM监视工具:使用JVM监视工具来监控和诊断JVM的性能和内存情况,以便及时发现问题并进行调优。常见的工具包括JConsole、VisualVM和GCViewer等。
总之,JVM内存分配调优是一个复杂的过程,需要根据应用程序的特点和需求进行适当的调整。在进行内存分配调优时,应该先了解JVM的内存模型和各种内存区域的特点,然后根据实际情况选择合适的参数进行调整,并监控和诊断JVM的性能和内存情况,以便及时发现问题并进行调优。
SQL调优
常见的SQL调优方法:
- 优化数据模型:根据业务需求和数据特点,选择合适的数据模型和数据库架构,例如分区、索引、视图等。
- 优化查询语句:使用更有效的查询语句,例如使用EXPLAIN分析查询计划、避免在WHERE子句中使用非索引字段、使用UNION ALL代替UNION等。
- 优化数据库参数:调整数据库参数,例如缓冲区大小、连接数等,以提高数据库性能。
- 优化索引:使用合适的索引可以显著提高查询性能,例如在WHERE和ORDER BY子句中使用索引字段。
- 优化数据库硬件:升级硬件可以提高数据库性能,例如增加内存、使用更快的CPU和磁盘等。
- 优化数据库配置:调整数据库配置,例如调整缓冲区大小、连接数等,以提高数据库性能。
- 使用分区表:对于大型表,使用分区可以提高查询性能和管理便利性。
- 避免使用子查询:子查询可能会降低查询性能,可以使用JOIN代替子查询。
- 使用临时表:对于需要大量复杂计算的查询,可以使用临时表提高查询性能。
- 优化存储过程:存储过程是预编译的SQL语句,使用存储过程可以提高查询性能。
数据库
MongoDB
- 介绍
MongoDB是一个基于分布式文件存储的数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。它是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
- 安装
https://www.mongodb.com/try/download/community
可视化:MongoDB Compass
- 概念
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
- 数据类型
数据类型 | 描述 |
---|---|
String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
Boolean | 布尔值。用于存储布尔值(真/假)。 |
Double | 双精度浮点值。用于存储浮点值。 |
Min/Max keys | 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。 |
Array | 用于将数组或列表或多个值存储为一个键。 |
Timestamp | 时间戳。记录文档修改或添加的具体时间。 |
Object | 用于内嵌文档。 |
Null | 用于创建空值。 |
Symbol | 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。 |
Date | 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。 |
Object ID | 对象 ID。用于创建文档的 ID。唯一主键 |
Binary Data | 二进制数据。用于存储二进制数据。 |
Code | 代码类型。用于在文档中存储 JavaScript 代码。 |
Regular expression | 正则表达式类型。用于存储正则表达式。 |
- SpringBoot集成
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
spring:
application:
name: mongodb-demo
data:
mongodb:
uri: mongodb://peng:123456@127.0.0.1:27017/test?authSource=admin&authMechanism=SCRAM-SHA-1
package cn.peng.mongodb.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* {@code @Author:} weiyupeng
* {@code @Date:} 2023/11/25 10:24
*/
@Document(collection = "user")
public class User {
@Id
private String id;
private String name;
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package cn.peng.mongodb.repo;
import cn.peng.mongodb.model.User;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* {@code @Author:} weiyupeng
* {@code @Date:} 2023/11/25 10:26
*/
public interface UserRepository extends MongoRepository<User, String> {
}
package cn.peng.mongodb.service;
import cn.peng.mongodb.model.User;
import cn.peng.mongodb.repo.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* {@code @Author:} weiyupeng
* {@code @Date:} 2023/11/25 10:27
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User saveUser(User user) {
return userRepository.save(user);
}
public List<User> findAllUsers() {
return userRepository.findAll();
}
}
package cn.peng.controller;
import cn.peng.mongodb.model.User;
import cn.peng.mongodb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* {@code @Author:} weiyupeng
* {@code @Date:} 2023/11/25 10:27
*/
@RestController
public class MongoDbTestController {
@Autowired
private UserService userService;
@GetMapping("/mongo/insert")
public String insert() {
User user = new User();
user.setName("peng");
user.setAge(27);
userService.saveUser(user);
return "insert into mongodb success";
}
@GetMapping("/mongo/query")
public List<User> query() {
return userService.findAllUsers();
}
}
[
{
"id": "65615ec49009af6a060ff72f",
"name": "peng",
"age": 27
},
{
"id": "65615ed69009af6a060ff730",
"name": "peng",
"age": 27
},
{
"id": "65615ed79009af6a060ff731",
"name": "peng",
"age": 27
}
]