判断资源是否被引用,判断资源是否被循环引用

该博客介绍了如何通过设计一个名为GlobalReferenced的数据结构来解决方法间的循环引用问题。GlobalReferenced类包含了处理各种类型引用(如List、CSV和数组)的方法,并能检查是否存在循环引用,防止程序陷入死循环。此外,还提供了示例代码展示如何使用这个数据结构来检测和避免循环引用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在系统中,总是会遇到需要判断引用的场景,如自定义方法模块,方法A里面调用了方法B,方法B里调用了方法A,这样自己就会造成循环引用。甚至于方法A里面调用了方法B,方法B里调用了方法C,方法C里面调用了方法A。这样的乃至更深层次的循环调用,那该怎么办呢,我这里设计了一个数据结构,来解决这个问题。

GlobalReferenced

/**
 * @author LaiYongBin
 * @date 创建于 2022/4/14 20:54
 * @apiNote 公用的引用数据结构
 */
public class GlobalReferenced {
    /**
     * 存储本地公共变量
     */
    private static final ThreadLocal<Map<String, GlobalReferenced>> THREAD_LOCAL = new ThreadLocal<>();


    /**
     * 以list作为参数
     */
    private static boolean isItReferenceList(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
        for (GlobalReferencedDto gl : globalReferencedDtoList) {
            if (gl.getReferencedList() == null) {
                gl.setReferencedList(new ArrayList<>(0));
            }
        }
        return isItReference(globalReferencedDtoList, nowId);
    }

    private static List<String> isItReferenceList(List<GlobalReferencedDto> globalReferencedDtoList, String[] ids) throws PaException {
        return isItReference(globalReferencedDtoList, ids);
    }

    /**
     * 以csv作为参数
     */
    private static boolean isItReferenceCsv(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
        for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
            List<String> referencedList = new ArrayList<>();
            String referencedCsv = globalReferencedDto.getReferencedCsv();
            if (StringUtils.isNotBlank(referencedCsv)) {
                for (String referenced : referencedCsv.split(",")) {
                    referencedList.add(referenced.trim());
                }
            }
            globalReferencedDto.setReferencedList(referencedList);
        }
        return isItReference(globalReferencedDtoList, nowId);
    }

    /**
     * 以csv作为参数
     */
    private static List<String> isItReferenceCsv(List<GlobalReferencedDto> globalReferencedDtoList, String[] ids) throws PaException {
        for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
            List<String> referencedList = new ArrayList<>();
            String referencedCsv = globalReferencedDto.getReferencedCsv();
            if (StringUtils.isNotBlank(referencedCsv)) {
                for (String referenced : referencedCsv.split(",")) {
                    referencedList.add(referenced.trim());
                }
            }
            globalReferencedDto.setReferencedList(referencedList);
        }
        return isItReference(globalReferencedDtoList, ids);
    }

    /**
     * 以数组作为参数
     */
    private static boolean isItReferenceArray(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
        for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
            List<String> referencedList = new ArrayList<>();
            String[] referencedArray = globalReferencedDto.getReferencedArray();
            if (referencedArray != null) {
                for (String referenced : referencedArray) {
                    referencedList.add(referenced.trim());
                }
            }
            globalReferencedDto.setReferencedList(referencedList);
        }
        return isItReference(globalReferencedDtoList, nowId);
    }

    private static List<String> isItReferenceArray(List<GlobalReferencedDto> globalReferencedDtoList, String[] ids) throws PaException {
        for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
            List<String> referencedList = new ArrayList<>();
            String[] referencedArray = globalReferencedDto.getReferencedArray();
            if (referencedArray != null) {
                for (String referenced : referencedArray) {
                    referencedList.add(referenced.trim());
                }
            }
            globalReferencedDto.setReferencedList(referencedList);
        }
        return isItReference(globalReferencedDtoList, ids);
    }

    /**
     * 判断某个id是否被引用, 如果被引用,则返回true, 否则返回false, 若是循环引用,则抛出异常
     *
     * @param globalReferencedDtoList GlobalReferencedDtoList是所有pojo的list, 里面有id属性, List里面需要包括判断的id的pojo
     *                                GlobalReferencedDto除了id属性外,还有一个referenceIdCsv属性, 属性值是它引用的id的csv字符串
     *                                我使用的是split后遍历添加,这里的逻辑可以根据自己的来自行修改
     * @param nowId                   需要判断是否被引用的id
     */
    private static boolean isItReference(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
        try {
            for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
                putValue(globalReferencedDto.getId(), new GlobalReferenced(globalReferencedDto.getId(), globalReferencedDto.getName()));
            }
            for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
                String flinkCepId = globalReferencedDto.getId();
                for (String referenceId : globalReferencedDto.getReferencedList()) {
                    getValue(flinkCepId).addTheIdPointsToList(referenceId.trim());
                    THREAD_LOCAL.get().get(flinkCepId).addTheIdPointsToList(referenceId.trim());
                }
            }
            // 判断是否有循环引用
            GlobalReferenced referenced = getValue(nowId);
            referenced.isItReference(nowId, referenced.id, referenced.name);
            return referenced.otherToMe.size() > 0;
        } finally {
            THREAD_LOCAL.remove();
        }
    }

    /**
     * 向THREAD_LOCAL中添加一个值
     */
    private static void putValue(String key, GlobalReferenced value) {
        Map<String, GlobalReferenced> map = THREAD_LOCAL.get();
        if (map == null) {
            map = new HashMap<>();
        }
        map.put(key, value);
        THREAD_LOCAL.set(map);
    }

    /**
     * 向THREAD_LOCAL中添加一个值
     */
    private static GlobalReferenced getValue(String key) {
        return THREAD_LOCAL.get().get(key);
    }

    /**
     * 判断某几个id是否被引用, 如果有引用,则返回被引用的列表
     *
     * @param referencedDto GlobalReferencedDtoList是所有pojo的list, 里面有id属性, List里面需要包括判断的id的pojo
     *                      GlobalReferencedDto除了id属性外,还有一个referenceIdCsv属性, 属性值是它引用的id的csv字符串
     *                      我使用的是split后遍历添加,这里的逻辑可以根据自己的来自行修改
     * @param ids           id的列表
     */
    private static List<String> isItReference(List<GlobalReferencedDto> referencedDto, String[] ids) throws PaException {
        try {
            for (GlobalReferencedDto dto : referencedDto) {
                putValue(dto.getId(), new GlobalReferenced(dto.getId(), dto.getName()));
            }
            for (GlobalReferencedDto ref : referencedDto) {
                String flinkCepId = ref.getId();
                for (String referenceId : ref.getReferencedList()) {
                    getValue(flinkCepId).addTheIdPointsToList(referenceId.trim());
                }
            }
            for (String nowId : ids) {
                if (getValue(nowId) == null) {
                    throw new PaException(String.format("未找到id为 `%s` 的cep模板", nowId));
                }
            }
            List<String> result = new ArrayList<>();
            for (String nowId : ids) {
                if (getValue(nowId).otherToMe.size() > 0) {
                    result.add(nowId);
                }
            }
            // 判断是否有循环引用
            return result;
        } finally {
            THREAD_LOCAL.remove();
        }
    }

    private GlobalReferenced(String id, String name) {
        this.id = id;
        this.otherToMe = new HashSet<>();
        this.meToOther = new HashSet<>();
        this.name = name;
    }

    private final String id;
    /**
     * 它指向的其它id
     */
    private final String name;
    private final Set<String> meToOther;
    /**
     * 指向它的id
     */
    private final Set<String> otherToMe;

    private void addTheIdPointsToList(String id) throws PaException {
        addTheIdPointsToList(id, false);
    }

    /**
     * 新增它指向的其它id
     */
    private void addTheIdPointsToList(String id, boolean isCalled) throws PaException {
        if (otherToMe.contains(id)) {
            throw new PaException(id + "不能相互引用");
        }
        meToOther.add(id);
        if (isCalled) {
            return;
        }
        // 将自己的引用添加到其它id的引用中
        GlobalReferenced cepReferenced = getValue(id);
        if (cepReferenced == null) {
            throw new PaException(id + "对应的记录不存在");
        }
        cepReferenced.addIdPointingToItList(this.id, true);
    }

    public void addIdPointingToItList(String id) throws PaException {
        addIdPointingToItList(id, false);
    }

    /**
     * 新增指向它的id
     */
    public void addIdPointingToItList(String id, boolean isCalled) throws PaException {
        if (meToOther.contains(id)) {
            throw new PaException(id + "不能相互引用");
        }
        otherToMe.add(id);
        if (isCalled) {
            return;
        }
        // 将自己的引用添加到其它id的引用中
        GlobalReferenced cepReferenced = getValue(id);
        if (cepReferenced == null) {
            throw new PaException(id + "对应的记录不存在");
        }
        cepReferenced.addTheIdPointsToList(this.id, true);
    }

    /**
     * 判断是否循环引用
     *
     * @param compareId 判断的id
     */
    private void isItReference(String compareId, String chainId, String chainName) throws PaException {
        // 遍历自己指向的id
        GlobalReferenced referenced = getValue(this.id);
        for (String otherId : referenced.meToOther) {
            GlobalReferenced cepReferenced = getValue(otherId);
            // 判断是否循环引用
            String nextChainName = chainName + " -> " + cepReferenced.name;
            String nextChainId = chainId + " -> " + cepReferenced.id;
            if (cepReferenced.getId().equals(compareId)) {
                if (cepReferenced.name != null) {
                    throw new PaException(String.format("id为 `%s` 的资源被循环引用了, 引用链条名为 `%s`", cepReferenced.id, nextChainName));
                }
                throw new PaException(String.format("id为 `%s` 的资源被循环引用了, 引用链条Id为 `%s`", cepReferenced.id, nextChainId));
            }
            cepReferenced.isItReference(compareId, nextChainId, nextChainName);
        }
    }

    public void isItReference() throws PaException {
        isItReference(this.id, this.id, this.name);
    }

    @Override
    public String toString() {
        return "FlinkCepReferenced{" + "id='" + id + '\'' + ", meToOther=" + meToOther + ", otherToMe=" + otherToMe + '}';
    }

    public String getId() {
        return id;
    }

    public Set<String> getMeToOther() {
        return meToOther;
    }

    public Set<String> getOtherToMe() {
        return otherToMe;
    }


    public static void main(String[] args) throws PaException {
        GlobalReferencedDto cepReferencedA = GlobalReferencedDto.builder().id("A").referencedCsv("B").build();
        GlobalReferencedDto cepReferencedB = GlobalReferencedDto.builder().id("B").referencedCsv("C").build();
        GlobalReferencedDto cepReferencedC = GlobalReferencedDto.builder().id("C").referencedCsv("A").build();
        System.out.println(isItReferenceCsv(Arrays.asList(cepReferencedA, cepReferencedB, cepReferencedC), "A"));
    }
    
}

GlobalReferencedDto.java

/**
 * @author LaiYongBin
 * @date 创建于 2022/4/19 10:37
 * @apiNote 公用的引用数据结构DTO
 */
@Data
@Builder
public class GlobalReferencedDto {

    private String id;

    private String name;
    /**
     * 以csv格式存储它引用的id, 为了兼容不同格式的引用
     */
    private String referencedCsv;
    /**
     * 以List格式存储它引用的id, 为了兼容不同格式的引用
     */
    private List<String> referencedList;
    /**
     * 以数组格式存储它引用的id, 为了兼容不同格式的引用
     */
    private String[] referencedArray;
}

### JVM 垃圾回收器判断对象可回收性的机制 JVM 的垃圾回收器通过一系列算法和条件来判断对象是否可以被回收。这些条件通常基于对象的引用状态以及其在内存中的存活情况。 #### 引用计数法 一种简单的判断对象是否可回收的方式是 **引用计数法**,即为每个对象维护一个引用计数器。当有一个新的引用指向该对象时,计数加一;当某个引用失效时,计数减一。如果计数值降为零,则表示没有任何引用指向此对象,因此它被认为是可回收的[^3]。 然而,这种方法存在循环引用的问题,可能导致某些实际上已经不再使用的对象无法被正确回收。 #### 可达性分析算法 为了克服引用计数法的缺陷,现代 JVM 主要采用 **可达性分析算法**(Reachability Analysis)。这种算法从一组称为“GC Roots”的根节点集合出发,沿着引用链向下搜索所有可达的对象。任何未被引用链触及到的对象都被视为不可达,从而成为候选回收目标[^1]。 常见的 GC Root 类型包括但不限于: - 虚拟机栈中局部变量表内的引用; - 方法区中类静态属性所持有的引用; - 方法区中常量池里的引用; - 本地方法栈中 JNI 所持的引用。 一旦确认某对象不可达,进一步还需要验证是否有其他特殊原因阻止它的立即销毁,比如 finalize() 方法的存在与否及其执行结果可能影响最终决定。 #### 不同类型的引用对回收的影响 除了基本的强引用外,Java 还定义了几种特殊的弱软虚引用形式,它们各自对应着不同强度级别的关联关系,并间接决定了何时适合触发相应的清理动作: - **Soft Reference**: 在内存不足之前不会轻易丢弃此类数据项。 - **Weak Reference**: 即使还有少量可用空间也会尽快处理掉这类实例。 - **Phantom Reference**: 更倾向于配合特定事件通知机制而非直接参与常规意义上的资源管理流程之中。 综上所述,JVM 使用多种技术和手段综合评估哪些对象应该进入下一轮清扫范围之内并实际实施操作过程。 ```java // 示例代码展示如何创建不同类型引用 import java.lang.ref.*; public class RefDemo { public static void main(String[] args) throws InterruptedException { Object obj = new Object(); // 创建一个新对象 SoftReference<Object> softRef = new SoftReference<>(obj); WeakReference<Object> weakRef = new WeakReference<>(obj); PhantomReference<Object> phantomRef = new PhantomReference<>(obj, null); obj = null; // 断开原始强引用 System.out.println(softRef.get()); // 输出可能是null或者原对象 System.out.println(weakRef.get()); // 很大概率已经是null System.out.println(phantomRef.get());// 总返回null } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原来你是小幸运

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值