[Java 8] (6) Lambda与资源管理

[Java 8] (6) Lambda与资源管理

大家好,又见面了,我是全栈君。

资源处理

Java本身自带了垃圾回收(Garbage Collection)功能。可是仅仅有垃圾回收的目标是内部资源(Internal Resource),典型的比方堆上分配的内存区域等。对于外部资源(External Resource),如数据库连接,文件句柄,套接字等资源,还是须要在程序中进行显式回收的。

使用Lambda表达式能够实现一种叫做Execute Around的模式,用来处理外部资源的回收。关于Execute Around模式,能够參考这个链接

回收资源

以下是一个利用FileWriter完毕消息写入的样例:

public class FileWriterExample {
    private final FileWriter writer;
    public FileWriterExample(final String fileName) throws IOException {
        writer = new FileWriter(fileName);
    }
    public void writeStuff(final String message) throws IOException {
        writer.write(message);
    }
    public void finalize() throws IOException {
        writer.close();
    }
    //...
}

public static void main(final String[] args) throws IOException {
    final FileWriterExample writerExample = new FileWriterExample("peekaboo.txt");
    writerExample.writeStuff("peek-a-boo");
}

可是执行以上的main方法后会发现。文件peekaboo.txt尽管被创建了,可是它是空的。出现这样的情况的原因在于文件并没有被关闭,也就是说finalize方法没有被调用。

这种方法是由JVM负责调用的。这里没有调用是由于JVM觉得此刻还有足够的内存,不须要执行finalize操作用来回收。毕竟垃圾回收操作也是须要消耗时间的。并且还是一种“Stop-the-world”(停下全部正在执行的应用程序代码)的方式。

关于垃圾回收的基础知识,能够參考这篇文章

实际上,在《Effective Java》这本书中。明白的指出了不要依赖于finalize方法来运行资源的回收。以上的代码违背这一准则。

关闭资源

更好的方式是直接调用资源的close方法用来回收外部资源:

public void close() throws IOException {
    writer.close();
}

final FileWriterExample writerExample = new FileWriterExample("peekaboo.txt");
writerExample.writeStuff("peek-a-boo");
writerExample.close();

调用以上的代码后,文件里确实有内容了。可是这样的做法还是有问题。假设在调用writeStuff方法的时候就发生了异常,那么close方法就没有机会被运行了。

确保资源的关闭

能够将close方法的调用放到finally语句中:

final FileWriterExample writerExample = new FileWriterExample("peekaboo.txt");
try {
    writerExample.writeStuff("peek-a-boo");
} finally {
    writerExample.close();
}

这样的写法也是眼下十分主流的写法,非常多代码都是这样处理外部资源的。可是不认为这段代码噪声过多了。不够简洁吗?针对这样的问题,Java 7中引入了自己主动资源管理(ARM,Automatic Resource Management)这一特性。

它使用了一种特殊形式的try语句,编译器会自己主动地将包括close方法调用的finally语句块插入到try的最后。以下是一个样例:

try(final FileWriterARM writerARM = new FileWriterARM("peekaboo.txt")) {
    writerARM.writeStuff("peek-a-boo");
    System.out.println("done with the resource...");
}

当try语句块运行完成之后,writeARM这一资源就会被关闭。

然而并非全部的资源都可以利用ARM进行自己主动回收的,须要该资源类实现AutoCloseable接口,当中值包括了一个方法:close()。在Java 8中,Stream接口实现了AutoCloseable接口,也就意味着基于I/O的Stream也可以利用ARM来实现资源的自己主动回收。

为了使用ARM,又一次实现的FileWriterARM例如以下:

public class FileWriterARM implements AutoCloseable {
    private final FileWriter writer;
    public FileWriterARM(final String fileName) throws IOException {
        writer = new FileWriter(fileName);
    }
    public void writeStuff(final String message) throws IOException {
        writer.write(message);
    }
    public void close() throws IOException {
        System.out.println("close called automatically...");
        writer.close();
    }
    //...
}

ARM确实简化了代码,可是仍然须要开发者去显示的调用它。

假设没有调用。程序除了不会关闭资源外。也不会出现什么其它错误。因此。能够对它进行进一步的优化。

使用Lambda表达式来回收资源

之前介绍的ARM有两个基本的缺点:

  1. 资源须要实现AutoCloseable接口
  2. 须要显式地使用它

以下我们看看怎样使用Lambda表达式结合Execute Around模式来进行优化:

public class FileWriterEAM {
    private final FileWriter writer;
    private FileWriterEAM(final String fileName) throws IOException {
        writer = new FileWriter(fileName);
    }
    private void close() throws IOException {
        System.out.println("close called automatically...");
        writer.close();
    }
    public void writeStuff(final String message) throws IOException {
        writer.write(message);
    }
    //...
}

能够发现。这个资源类的构造函数被声明成私有的了。也就意味着外部代码不能直接创建这样的资源。

close方法也被声明为私有的。仅仅有writeStuff是公有的方法。

我们须要一个工厂方法来得到该资源类的实例。这一点能够通过静态方法结合Lambda表达式来办到:

public static void use(final String fileName,
    final UseInstance<FileWriterEAM, IOException> block) throws IOException {
    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);
    try {
        block.accept(writerEAM);
    } finally {
        writerEAM.close();
    }
}

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
    void accept(T instance) throws X;
}

这个静态工厂方法和传统意义上的静态工厂方法不太一样。它并没有返回被创建的实例,而是马上在方法中使用了被创建的实例。

use方法接受的第二个參数是UseInstance类型的函数接口,它和JDK中的Consumer很类似。仅仅只是它可以抛出一个异常。关于这一点,在之前的文章中进行了介绍。

另外还能够将ARM融合到上面的代码中:

public static void use(final String fileName,
    final UseInstance<FileWriterEAM, IOException> block) throws IOException {
    try(final FileWriterEAM writerEAM = new FileWriterEAM(fileName)) {
        block.accept(writerEAM);
    }
}

仅仅只是此时须要FileWriterEAM实现AutoCloseable接口,并将之前的close方法訪问级别从私有变成公有。

使用它也很easy:

// case 1
FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

// case 2
FileWriterEAM.use("eam2.txt", writerEAM -> {
    writerEAM.writeStuff("how");
    writerEAM.writeStuff("sweet");
});

这样的模式克服了之前提到的首要缺点。即须要显式调用try with resource语句进行资源回收。而且它对资源对象的生命周期也进行了非常好的控制,因此它也实现了Loan模式。仅仅有在须要使用一个资源的时候才会创建它,而且在利用完成之后马上将它标记为回收。

锁管理

在并发程序中,锁是一类相当重要的资源,以下我们看看Lambda表达式怎样处理锁资源。

历史悠久的synchronized代码块实际上就是一个典型的Execute Around模式的实现。synchronized关键词的出现能保证同一时刻至多仅仅有一个线程可以执行这段代码。

可是synchronizedkeyword也有其缺点:

  1. synchronized代码块难以进行超时处理
  2. synchronized代码块难以进行单元測试

因此为了解决这些问题,Lock接口应运而生。Lock接口可以处理超时的情况。而且由于其本身是一个接口,也easy被Mocking而完毕单元測试。可是天下没有免费的午餐。使用Lock时须要显式地进行加锁和解锁操作。

可是在Java 8中,能够使用Lambda表达式结合前面提到的Execute Around模式来轻松解决这一类问题,以下是一段使用了Lock的代码:

public class Locking {
    Lock lock = new ReentrantLock(); //or mock
    protected void setLock(final Lock mock) {
        lock = mock;
    }
    public void doOp1() {
        lock.lock();
        try {
            //...critical code...
        } finally {
            lock.unlock();
        }
    }
    //...
}

上述的doOp1方法噪声太多。过多的加锁解锁和try finally语句块让代码的意图不够清晰。为了使用Lambda表达式。我们能够首先设计一段代码:

public class Locker {
    public static void runLocked(Lock lock, Runnable block) {
        lock.lock();
        try {
            block.run();
        } finally {
            lock.unlock();
        }
    }
}

上述代码将加锁解锁操作和固定的try finally语句块给抽象成一个方法,然后将真正须要在锁环境中执行的代码通过一个Runnable參数传入。这样一来,其他须要锁环境的操作就能够这样实现了:

public void doOp2() {
    runLocked(lock, () -> {/*...critical code ... */});
}
public void doOp3() {
    runLocked(lock, () -> {/*...critical code ... */});
}
public void doOp4() {
    runLocked(lock, () -> {/*...critical code ... */});
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/116286.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • ping命令和tracert命令的作用_ping命令有哪些

    ping命令和tracert命令的作用_ping命令有哪些本文只是总结了两个常用的网络命令的实现原理和一点使用经验说明。这些东西通常都分布在各种书籍或者文章中的,我勤快那么一点点,总结一下,再加上我的一点理解和使用经验,方便大家了解。这些也是很基础的东西,没什么高深的。Ping这个应该大家都会用的吧,最主要的就是检测目标主机是不是可连通。Ping程序实际就是发送一个ICMP回显请求报文(就是请求别人收到这个报文之后回显)给目的主机,并等待回显的ICM…

    2022年9月24日
    2
  • 配置 PyCharm for Linux 设置启动图标 pycharm-edu-2021.3.1 Ubuntu 18.04.6 LTS

    配置 PyCharm for Linux 设置启动图标 pycharm-edu-2021.3.1 Ubuntu 18.04.6 LTS安装PyCharm下载PyCharm教育版,使用tar-zxvf命令将其解压到你希望的软件安装位置,解压完就是安装完了,要选择一个你有全部权限的目录,一般在自己家目录下挑选位置即可。cd到安装目录下,执行以下命令运行PyCharm,第一次运行会有一些选择内容,按实际选择即可。cdbin/./pycharm.sh安装完成!自定义图标固定到收藏夹在下面两个路径中任选,创建matlab.desktop文件。前者是系统全局的,后者是当前用户私有的/usr/share/appl

    2025年8月23日
    3
  • 工信部表态支持Linux,可是Linux又是什么呢?

    工信部表态支持Linux,可是Linux又是什么呢?

    2022年1月23日
    66
  • win10台式机一根网线连接笔记本wifi网络

    win10台式机一根网线连接笔记本wifi网络需求:目前情况:win10笔记本电脑有无线网,win10台式机没法连接无线,现在有一条网线。需要达到的效果:通过网线连接笔记本和台式机,笔记本设置共享网络,那么台式机通过网线获取笔记本共享的网络就可以上网了。一、笔记本电脑需要设置【允许其他网络用户通过此计算机的Internet连接来连接】具体操作步骤如下:1、在设置中搜索控制面板,打开即可2、打开【网络和共享中心】3、点击【更改适配器设置】4、选择【WLAN】右键点击【WLAN】——属性5、.

    2022年6月26日
    128
  • beanutils工具类_beanutils.copyproperties忽略null

    beanutils工具类_beanutils.copyproperties忽略null什么是BeanUtils工具BeanUtils工具是一种方便我们对JavaBean进行操作的工具,是Apache组织下的产品。BeanUtils工具一般可以方便javaBean的哪些操作?1)bean

    2022年8月5日
    8
  • Hashtable 的实现原理

    Hashtable 的实现原理

    2021年5月10日
    220

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号