Use try with resources to close resources gracefully

Keywords: Programming Java Jedis Redis

We know that in the process of Java programming, if external resources (file, database connection, network connection, redis, etc.) are opened, we must manually close these external resources after they are used.

Because the external resources are not managed by the JVM, we can't enjoy the garbage collection mechanism of the JVM. If we don't make sure to shut down the external resources at the right time when programming, it will lead to the leakage of the external resources. Then there will be many serious problems, such as the abnormal occupation of files, the overflow of the connection pool due to too many database connections * *.

Resource shutdown mode before JDK7

/**
 * jdk7 How streams were previously closed
 * */
public class CloseResourceBefore7 {
    private static final String FileName = "file.txt";

    public static void main(String[] args) throws IOException {
        FileInputStream inputStream = null;

        try {
            inputStream = new FileInputStream(FileName);
            char c1 = (char) inputStream.read();
            System.out.println("c1=" + c1);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

Close with try with resource syntax after JDK7

Before JDK7, Java didn't automatically turn off the syntax feature of external resources. Until the try with resource syntax was added in JDK7, it realized this function.

Definition of try with resource resource: all the objects that implement java.lang.autoclosable interface (including all the objects that implement java.io.Closeable) can be used as resources.

1. An example

A Resource class that implements the java.lang.autoclosable interface:

/**
 * Resource class
 * */
public class Resource implements AutoCloseable {
    public void sayHello() {
        System.out.println("hello");
    }

    @Override
    public void close() throws Exception {
        System.out.println("Resource is closed");
    }
}

Test class CloseResourceIn7.java

/**
 * jdk7 And how to close the flow later
 * */
public class CloseResourceIn7 {
    public static void main(String[] args) {
        try(Resource resource = new Resource()) {
            resource.sayHello();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Print results:

hello
Resource is closed

When there are multiple open resources: Resource2.java

/**
 * Resources 2
 * */
public class Resource2 implements AutoCloseable {
    public void sayhello() {
        System.out.println("Resource say hello");
    }

    @Override
    public void close() throws Exception {
        System.out.println("Resource2 is closed");
    }
}

Test class CloseResourceIn7.java

/**
 * jdk7 And how to close the flow later
 * */
public class CloseResourceIn7 {
    public static void main(String[] args) {
        try(Resource resource = new Resource(); Resource2 resource2 = new Resource2()) {
            resource.sayHello();
            resource2.sayhello();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Result:

hello
hello2
Resource2 is closed
Resource is closed

Note: when opening multiple resources, the closing sequence is reversed

principle

View the compiled class file CloseResourceIn7.class

public class CloseResourceIn7 {
    public CloseResourceIn7() {
    }

    public static void main(String[] args) {
        try {
            Resource resource = new Resource();
            Throwable var2 = null;
            try {
                resource.sayHello();
            } catch (Throwable var12) {
                var2 = var12;
                throw var12;
            } finally {
                if (resource != null) {
                    if (var2 != null) {
                        try {
                            resource.close();
                        } catch (Throwable var11) {
                            var2.addSuppressed(var11);
                        }
                    } else {
                        resource.close();
                    }
                }
            }
        } catch (Exception var14) {
            var14.printStackTrace();
        }
    }
}

It can be found that var2.addresssupplied (var11) in try catch finally statement block finally is generated after compilation to handle exception shielding.

Let's modify the code to Resource.java to throw exceptions in both methods

/**
 * Resource class
 * */
public class Resource implements AutoCloseable {
    public void sayHello() throws Exception {
        throw new Exception("Resource throw Exception");
    }

    @Override
    public void close() throws Exception {
        throw new Exception("Close method throw Exception");
    }
}

Test class CloseResourceBefore7.java

/**
 * jdk7 How streams were previously closed
 * */
public class CloseResourceBefore7 {

    public static void main(String[] args) {
        try {
            errorTest();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void errorTest() throws Exception {
        Resource resource = null;
        try {
            resource = new Resource();
            resource.sayHello();
        }

        finally {
            if (resource != null) {
                resource.close();
            }
        }
    }
}

Print results:

java.lang.Exception: Close method throw Exception
	at com.shuwen.Resource.close(Resource.java:15)
	at com.shuwen.CloseResourceIn7.errorTest(CloseResourceIn7.java:27)
	at com.shuwen.CloseResourceIn7.main(CloseResourceIn7.java:12)

Only the last closing exception [exception shielding] will be printed, which will cause some difficulties for developers to check errors. Let's change to try with resource method to implement CloseResourceIn7.java

/**
 * jdk7 And how to close the flow later
 * */
public class CloseResourceIn7 {

    public static void main(String[] args) {
        try {
            errorTest();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void errorTest() throws Exception {
        try(Resource resource = new Resource()) {
            resource.sayHello();
        }

    }
}

Print information:

java.lang.Exception: Resource throw Exception
	at com.shuwen.Resource.sayHello(Resource.java:10)
	at com.shuwen.CloseResourceIn7.errorTest(CloseResourceIn7.java:20)
	at com.shuwen.CloseResourceIn7.main(CloseResourceIn7.java:12)
	Suppressed: java.lang.Exception: Close method throw Exception
		at com.shuwen.Resource.close(Resource.java:15)
		at com.shuwen.CloseResourceIn7.errorTest(CloseResourceIn7.java:21)
		... 1 more

It can be found that there is an additional prompt of "Suppressed" in the exception information, which tells us that the exception is actually composed of two exceptions. The exception of "Close method throw Exception" is an exception that is "shielded".

Application: use in Jedis

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisTest {

  public static void main(String[] args) {
    JedisPool pool = new JedisPool();
    try (Jedis jedis = pool.getResource()) { // Use up auto close
      doSomething(jedis);
    }
  }

  private static void doSomething(Jedis jedis) {
    // code it here
  }
}

In this way, the Jedis object will definitely be returned to the connection pool (except for the dead loop), so as to avoid the tragedy of application stuck.

Posted by bladecatcher on Tue, 10 Dec 2019 16:08:38 -0800