前因
最近线上环境经常出现OOM,导致系统崩溃,在排查过程中,发现代码在使用多线程时有很多不规范的地方,导致内存泄露,因此我决定整理出一些常用的示例,在使用时 直接套用即可。因个人技术水平有限,如有错误,望指正。
宗旨
代码可以不是最高效和最简洁的,但一定会是最安全和清晰明了的。
1. 流什么情况下需要手动关闭
为什么要关闭流
一个对象在没有被引用变量指向的时候它会变成垃圾,最终会被垃圾回收器从内存中清除,对于我们创建的流对象,干嘛还要"调用close方法将其进行关闭呢,以释放与其相关的资源"呢?
当我们在程序中创建一个IO流对象的时候,同时系统也会创建一个叫做流的东西,在这种情况下,计算机内存中实际产生了两个事物,一个是java程序中类的实例对象,一个是系统本身产生的某种资源,而java垃圾回收器只能管理程序
中类的实例对象,没办法去管理系统产生的资源,所以程序需要调用close方法,去通知系统释放其自身产生的资源。
什么情况下需要手动关闭流
- 指向内存的流可以不用关闭,指向存储卡/硬盘的流一定要关闭!
如果是内存读写流,它内部是使用字节数组读内存的,这个字节数组是它的成员变量,当这个数组不再使用变成垃圾的时候,Java的垃圾回收机制会将它回收。所以不需要关流。 如:ByteArrayOutputStream或ByteArrayInputStream
如果是指向存储卡/硬盘的流,因为系统会产生流,如果不关闭则会导致内存泄露。如:FileOutputStream。
- JDK1.7之后 很多流都继承了 Closeable或AutoCloseable 接口,如果继承了,则无需手动关闭。 因为 Closeable 也继承了 AutoCloseable 接口。如果继承了 Closeable 也间接继承了 AutoCloseable 接口。
AutoCloseable接口位于java.lang包下,从JDK1.7开始引入。可以针对于所有实现该接口的流,而closable本身也实现了该接口,java的io流间接性的可以自动关闭接口,也就是说从jdk1.7开始,不需要手动去关流。
如何正确关闭流
JDK1.7之前 try{} finally{} 在finally中释放资源。
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("xxx.txt");
fos = new FileOutputStream("kkk.txt");
int len = -1;
while((len=fis.read())!=-1) {
fos.write(len);
}
} finally {
try {
if (fis!=null)
fis.close();
} finally {
if (fis!=null)
fos.close();
}
}
JDK1.7之后采用 {try}-with-resources的解释,将可能抛出异常的代码块放入到try块中,在try结束的时候,会自动将这些资源关闭(调用close方法)。
try(
FileInputStream fis = new FileInputStream("xxx.txt");
FileOutputStream fos = new FileOutputStream("hhh.txt");
MyAutoClosable closable = new MyAutoClosable();
){
int len = -1;
while((len=fis.read())!=-1) {
fos.write(len);
}
}
注意:这二者 try 后面紧跟符号不一样!!!!
建议
只要是流,用完后都在finally块里调用其close()方法进行关闭。万一你记混了,认为不需要close,项目上生产环境出了问题,相信我,你会哭的哦-。-
2. Dubbo 项目内部 引用 同项目的Service 用 @Reference 还是 @Autowired
3. 异步调用
CountDownLatch countDownLatch = new CountDownLatch(completeTaskDTO.getTaskIds().size());
// 因为事务在call方法中,所以如果线程这个方法被中断,可能导致当前任务的事务不会回滚
List<Future<Result<Boolean>>> futures = new ArrayList<>();
Callable<Result<Boolean>> callable = null;
for (Long taskId : completeTaskDTO.getTaskIds()) {
try {
// 调用内部类
callable = new CompleteUserTaskAsync(taskId);
Future<Result<Boolean>> submit = visiableThreadPoolTaskExecutor.submit(callable);
futures.add(submit);
} catch (Exception e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
List<Result<Boolean>> responseResults = new ArrayList<>();
for (Future<Result<Boolean>> result : futures) {
while (true) {
//CPU高速轮询:每个future都并发轮循,判断完成状态然后获取结果,这一行,是本实现方案的精髓所在。即有10个future在高速轮询,完成一个future的获取结果,就关闭一个轮询
if (result.isDone() && !result.isCancelled()) {
//获取future成功完成状态,如果想要限制每个任务的超时时间,取消本行的状态判断+future.get(1000*1, TimeUnit.MILLISECONDS)+catch超时异常使用即可。
try {
Result<Boolean> responseResult = result.get();//获取结果
responseResults.add(responseResult);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//当前future获取结果完毕,跳出while
break;
} else {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
try {
countDownLatch.await();
System.out.print("all thread task have finished,main thread will continue run");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 内部类
class CompleteUserTaskAsync implements Callable<Result<Boolean>> {
private Long taskId;
private CompleteUserTaskAsync(Long taskId) {
this.taskId = taskId;
}
@Override
public Result<Boolean> call() throws Exception {
return Result.responseSuccess(Boolean.TRUE);
}
- ReentrantLock 用法
* ReentrantLock lock = new ReentrantLock();
* lock.lock();
* try {
* // ... method body
* } finally {
* lock.unlock();
* }