如何创建类实现字符串及Docker Client连接的单次构建与复用?
你可以通过静态初始化或者单例模式来实现,核心就是让字符串只生成一次,之后每次用直接拿现成的就行,不用重复造轮子。
方式一:饿汉式静态初始化(类加载时就生成)
如果字符串的内容是固定的,或者启动时就能确定生成逻辑,直接用静态初始化块就行——类加载的时候JVM会帮你执行一次生成逻辑,之后这个字符串就一直存在了:
public class ReusableStringHolder { // 最终会被复用的字符串,用final保证不可修改 public static final String REUSABLE_STRING; // 静态初始化块,类加载时只跑一次 static { // 这里可以写任意复杂的生成逻辑,比如拼接、读配置、调用接口拿数据 StringBuilder sb = new StringBuilder(); sb.append("Hello, "); sb.append("Reusable "); sb.append("String!"); REUSABLE_STRING = sb.toString(); } }
用的时候直接调用ReusableStringHolder.REUSABLE_STRING就行,每次都是同一个实例,不会重复生成。
方式二:懒加载单例(用到才生成)
要是字符串生成逻辑比较费资源,或者不想启动时就占内存,可以用懒加载——第一次调用的时候才生成,之后直接复用:
public class LazyReusableStringHolder { private static String reusableString; // 私有构造,防止外部瞎实例化这个类 private LazyReusableStringHolder() {} // 对外提供获取字符串的方法 public static String getReusableString() { // 双重检查,避免多线程下重复生成 if (reusableString == null) { synchronized (LazyReusableStringHolder.class) { if (reusableString == null) { StringBuilder sb = new StringBuilder(); sb.append("Lazy "); sb.append("Loaded "); sb.append("String!"); reusableString = sb.toString(); } } } return reusableString; } }
这种方式只有第一次调用getReusableString()才会执行生成逻辑,之后直接返回现成的,适合资源消耗大的场景。
你的需求太常见了——每次调用都新建DockerClient确实很浪费资源,我们只要把DockerClient实例只创建一次,之后每次调用都返回这个实例就行,推荐几种靠谱的实现方式:
方式一:饿汉式静态实例(简单又安全)
直接在类加载时就创建好DockerClient,之后每次调用都返回这个现成的实例,JVM会帮你保证线程安全:
public class Docker { // 静态常量,类加载时初始化一次 private static final DockerClient DOCKER_CLIENT; static { try { Settings settings = Settings.getSettings(); // 补全你的配置获取逻辑 // 这里写DockerClient的构建代码,按你的实际需求加配置 DOCKER_CLIENT = DockerClientBuilder.getInstance() .withDockerHost(settings.getDockerHost()) .withDockerConfig(settings.getDockerConfig()) // 其他必要的配置项 .build(); } catch (Exception e) { // 初始化失败直接抛运行时异常,或者按你的需求处理 throw new RuntimeException("Failed to initialize DockerClient", e); } } public static DockerClient dockerClient() { return DOCKER_CLIENT; } }
这种方式最省心,适合配置固定、启动时就能初始化的场景,不用操心线程安全的问题。
方式二:双重检查锁懒加载(按需初始化)
要是你的DockerClient依赖的配置是动态的,或者不想启动就初始化,可以用这种懒加载方式,第一次调用时才创建实例:
public class Docker { // 用volatile防止指令重排序,保证多线程下的正确性 private static volatile DockerClient dockerClient; public static DockerClient dockerClient() { // 双重检查,既保证懒加载又保证线程安全 if (dockerClient == null) { synchronized (Docker.class) { if (dockerClient == null) { try { Settings settings = Settings.getSettings(); dockerClient = DockerClientBuilder.getInstance() .withDockerHost(settings.getDockerHost()) .build(); } catch (Exception e) { throw new RuntimeException("Failed to create DockerClient", e); } } } } return dockerClient; } }
这里的volatile关键字一定要加,不然多线程下可能会拿到半初始化的实例。
方式三:静态内部类单例(优雅的懒加载)
这是我个人比较喜欢的方式,利用JVM的类加载机制实现懒加载和线程安全,代码还简洁:
public class Docker { // 静态内部类,只有当getDockerClient()被调用时才会加载 private static class DockerClientHolder { private static final DockerClient INSTANCE; static { try { Settings settings = Settings.getSettings(); INSTANCE = DockerClientBuilder.getInstance() .withDockerHost(settings.getDockerHost()) .build(); } catch (Exception e) { throw new RuntimeException("Failed to initialize DockerClient", e); } } } public static DockerClient dockerClient() { return DockerClientHolder.INSTANCE; } }
内部类只有在被引用的时候才会加载,所以实现了懒加载,而且JVM会保证类加载的线程安全,不用手动加锁,非常优雅。
另外提一句:如果你的DockerClient有可能出现连接断开的情况,可以在返回实例前加个健康检查,要是连接失效了就重新初始化。但如果是常规的复用场景,上面的单例方式完全够用。
内容的提问来源于stack exchange,提问作者user9565299

