55import com .codingapi .springboot .script .repository .TempGroovyScriptRepositoryContext ;
66import lombok .Getter ;
77
8- import java .util .*;
8+ import java .util .List ;
9+ import java .util .Map ;
10+ import java .util .concurrent .ConcurrentHashMap ;
11+ import java .util .concurrent .Executors ;
12+ import java .util .concurrent .ScheduledExecutorService ;
13+ import java .util .concurrent .ScheduledFuture ;
14+ import java .util .concurrent .TimeUnit ;
915import java .util .stream .Collectors ;
1016
1117/**
@@ -16,41 +22,58 @@ public class TempGroovyScriptContext {
1622 @ Getter
1723 private final static TempGroovyScriptContext instance = new TempGroovyScriptContext ();
1824
25+ /**
26+ * 共享的清理调度线程(daemon)
27+ * 所有临时脚本共用一个调度线程,避免每个脚本创建原生线程导致线程泄漏
28+ */
29+ private final static ScheduledExecutorService SCHEDULER = Executors .newSingleThreadScheduledExecutor (r -> {
30+ Thread thread = new Thread (r , "temp-groovy-script-clear" );
31+ thread .setDaemon (true );
32+ return thread ;
33+ });
34+
1935 private final Map <String , ClearJob > cache ;
2036
2137 private TempGroovyScriptContext () {
22- this .cache = new HashMap <>();
38+ this .cache = new ConcurrentHashMap <>();
2339 }
2440
2541 private static class ClearJob {
2642
2743 @ Getter
2844 private final TempGroovyScript tempGroovyScript ;
2945
30- private final Timer timer ;
46+ private final ScheduledFuture <?> future ;
3147
3248 public ClearJob (TempGroovyScript groovyScript ) {
3349 this .tempGroovyScript = groovyScript ;
34- this .timer = new Timer ();
35- this .initTimer ();
50+ // clearTime 为绝对到期时间戳,需换算为相对延迟
51+ long delay = groovyScript .getClearTime () - System .currentTimeMillis ();
52+ this .future = SCHEDULER .schedule (this ::expire , delay , TimeUnit .MILLISECONDS );
3653 }
3754
3855 public GroovyScript getGroovyScript () {
3956 return this .tempGroovyScript .getGroovyScript ();
4057 }
4158
42- private void initTimer () {
43- this .timer .schedule (new TimerTask () {
44- @ Override
45- public void run () {
46- TempGroovyScriptContext .getInstance ().remove (getKey ());
47- }
48- }, tempGroovyScript .getClearTime ());
49- }
50-
5159 public String getKey () {
5260 return tempGroovyScript .getKey ();
5361 }
62+
63+ /**
64+ * 到期清理
65+ * 仅当缓存中仍是当前任务时才删除,避免过期任务误删已被刷新的脚本
66+ */
67+ private void expire () {
68+ TempGroovyScriptContext .getInstance ().removeIfCurrent (getKey (), this );
69+ }
70+
71+ /**
72+ * 取消定时清理任务
73+ */
74+ private void cancel () {
75+ this .future .cancel (false );
76+ }
5477 }
5578
5679 /**
@@ -61,7 +84,17 @@ public String getKey() {
6184 public void save (GroovyScript script ) {
6285 if (script != null ) {
6386 long tempValidTime = PropertiesContext .getInstance ().getTempValidTime ();
64- this .cache .put (script .getKey (), new ClearJob (new TempGroovyScript (script , tempValidTime + System .currentTimeMillis ())));
87+ this .put (script .getKey (), new ClearJob (new TempGroovyScript (script , tempValidTime + System .currentTimeMillis ())));
88+ }
89+ }
90+
91+ /**
92+ * 写入缓存,覆盖时取消旧任务的定时清理
93+ */
94+ private void put (String key , ClearJob job ) {
95+ ClearJob previous = this .cache .put (key , job );
96+ if (previous != null ) {
97+ previous .cancel ();
6598 }
6699 }
67100
@@ -74,7 +107,7 @@ public void loadAll(List<TempGroovyScript> groovyScripts) {
74107 if (groovyScript .isExpired ()) {
75108 this .remove (groovyScript .getKey ());
76109 } else {
77- this .cache . put (groovyScript .getKey (), new ClearJob (groovyScript ));
110+ this .put (groovyScript .getKey (), new ClearJob (groovyScript ));
78111 }
79112 }
80113 }
@@ -94,10 +127,23 @@ public List<TempGroovyScript> findAll() {
94127 * @param key 脚本key
95128 */
96129 public void remove (String key ) {
97- this .cache .remove (key );
130+ ClearJob job = this .cache .remove (key );
131+ if (job != null ) {
132+ job .cancel ();
133+ }
98134 TempGroovyScriptRepositoryContext .getInstance ().delete (key );
99135 }
100136
137+ /**
138+ * 仅当缓存中的任务仍为当前任务时删除,防止过期任务误删已被刷新的脚本。
139+ * 使用 ConcurrentHashMap.remove(key, value) 原子判断 + 删除,避免 check-then-act 竞态
140+ */
141+ private void removeIfCurrent (String key , ClearJob job ) {
142+ if (this .cache .remove (key , job )) {
143+ job .cancel ();
144+ TempGroovyScriptRepositoryContext .getInstance ().delete (key );
145+ }
146+ }
101147
102148 /**
103149 * 脚本总数量
@@ -119,7 +165,11 @@ public GroovyScript getGroovyScript(String key) {
119165 if (job == null ) {
120166 TempGroovyScript groovyScript = TempGroovyScriptRepositoryContext .getInstance ().get (key );
121167 if (groovyScript != null ) {
122- this .cache .put (key , new ClearJob (groovyScript ));
168+ if (groovyScript .isExpired ()) {
169+ this .remove (key );
170+ return null ;
171+ }
172+ this .put (key , new ClearJob (groovyScript ));
123173 return groovyScript .getGroovyScript ();
124174 }
125175 return null ;
@@ -132,6 +182,9 @@ public GroovyScript getGroovyScript(String key) {
132182 * 清空脚本数据
133183 */
134184 public void clear () {
185+ for (ClearJob job : this .cache .values ()) {
186+ job .cancel ();
187+ }
135188 this .cache .clear ();
136189 }
137- }
190+ }
0 commit comments