Browse Source

add LockUtil and fix Cache

Looly 5 years ago
parent
commit
0d7ef8f092

+ 1 - 0
CHANGELOG.md

@@ -16,6 +16,7 @@
 * 【core   】     CollUtil.newHashSet重载歧义,更换为set方法
 * 【core   】     增加ListUtil,增加Hash32、Hash64、Hash128接口
 * 【crypto 】     BCUtil增加readPemPrivateKey和readPemPublicKey方法
+* 【cache  】     替换读写锁为StampedLock,增加LockUtil
 
 ### Bug修复
 * 【core   】     修复NumberWordFormatter拼写错误(issue#799@Github)

+ 63 - 91
hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java

@@ -6,9 +6,7 @@ import cn.hutool.core.lang.func.Func0;
 
 import java.util.Iterator;
 import java.util.Map;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+import java.util.concurrent.locks.StampedLock;
 
 /**
  * 超时和限制大小的缓存的默认实现<br>
@@ -17,32 +15,39 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
  * <li>创建一个新的Map</li>
  * <li>实现 <code>prune</code> 策略</li>
  * </ul>
- * 
- * @author Looly,jodd
  *
  * @param <K> 键类型
  * @param <V> 值类型
+ * @author Looly, jodd
  */
 public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	private static final long serialVersionUID = 1L;
 
 	protected Map<K, CacheObj<K, V>> cacheMap;
 
-	private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
-	private final ReadLock readLock = cacheLock.readLock();
-	private final WriteLock writeLock = cacheLock.writeLock();
+	private final StampedLock lock = new StampedLock();
 
-	/** 返回缓存容量,<code>0</code>表示无大小限制 */
+	/**
+	 * 返回缓存容量,<code>0</code>表示无大小限制
+	 */
 	protected int capacity;
-	/** 缓存失效时长, <code>0</code> 表示无限制,单位毫秒 */
+	/**
+	 * 缓存失效时长, <code>0</code> 表示无限制,单位毫秒
+	 */
 	protected long timeout;
 
-	/** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */
+	/**
+	 * 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。
+	 */
 	protected boolean existCustomTimeout;
 
-	/** 命中数 */
+	/**
+	 * 命中数
+	 */
 	protected int hitCount;
-	/** 丢失数 */
+	/**
+	 * 丢失数
+	 */
 	protected int missCount;
 
 	// ---------------------------------------------------------------- put start
@@ -53,20 +58,19 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 
 	@Override
 	public void put(K key, V object, long timeout) {
-		writeLock.lock();
-
+		final long stamp = lock.writeLock();
 		try {
 			putWithoutLock(key, object, timeout);
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 	}
 
 	/**
 	 * 加入元素,无锁
-	 * 
-	 * @param key 键
-	 * @param object 值
+	 *
+	 * @param key     
+	 * @param object  
 	 * @param timeout 超时时长
 	 * @since 4.5.16
 	 */
@@ -85,8 +89,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	// ---------------------------------------------------------------- get start
 	@Override
 	public boolean containsKey(K key) {
-		readLock.lock();
-
+		final long stamp = lock.readLock();
 		try {
 			// 不存在或已移除
 			final CacheObj<K, V> co = cacheMap.get(key);
@@ -99,7 +102,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 				return true;
 			}
 		} finally {
-			readLock.unlock();
+			lock.unlockRead(stamp);
 		}
 
 		// 过期
@@ -111,24 +114,14 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	 * @return 命中数
 	 */
 	public int getHitCount() {
-		this.readLock.lock();
-		try {
-			return hitCount;
-		} finally {
-			this.readLock.unlock();
-		}
+		return hitCount;
 	}
 
 	/**
 	 * @return 丢失数
 	 */
 	public int getMissCount() {
-		this.readLock.lock();
-		try {
-			return missCount;
-		} finally {
-			this.readLock.unlock();
-		}
+		return missCount;
 	}
 
 	@Override
@@ -140,11 +133,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	public V get(K key, Func0<V> supplier) {
 		V v = get(key);
 		if (null == v && null != supplier) {
-			writeLock.lock();
+			final long stamp = lock.writeLock();
 			try {
 				// 双重检查锁
 				final CacheObj<K, V> co = cacheMap.get(key);
-				if(null == co || co.isExpired() || null == co.getValue()) {
+				if (null == co || co.isExpired()) {
 					try {
 						v = supplier.call();
 					} catch (Exception e) {
@@ -155,7 +148,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 					v = co.get(true);
 				}
 			} finally {
-				writeLock.unlock();
+				lock.unlockWrite(stamp);
 			}
 		}
 		return v;
@@ -163,23 +156,25 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 
 	@Override
 	public V get(K key, boolean isUpdateLastAccess) {
-		readLock.lock();
-
+		// 尝试读取缓存,使用乐观读锁
+		long stamp = lock.readLock();
 		try {
 			// 不存在或已移除
 			final CacheObj<K, V> co = cacheMap.get(key);
-			if (co == null) {
+			if (null == co) {
 				missCount++;
 				return null;
 			}
 
-			if (false == co.isExpired()) {
+			if (co.isExpired()) {
+				missCount++;
+			} else{
 				// 命中
 				hitCount++;
 				return co.get(isUpdateLastAccess);
 			}
 		} finally {
-			readLock.unlock();
+			lock.unlock(stamp);
 		}
 
 		// 过期
@@ -199,30 +194,32 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	@Override
 	public Iterator<CacheObj<K, V>> cacheObjIterator() {
 		CopiedIter<CacheObj<K, V>> copiedIterator;
-		readLock.lock();
+		final long stamp = lock.readLock();
 		try {
 			copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
 		} finally {
-			readLock.unlock();
+			lock.unlockRead(stamp);
 		}
 		return new CacheObjIterator<>(copiedIterator);
 	}
 
 	// ---------------------------------------------------------------- prune start
+
 	/**
-	 * 清理实现
-	 * 
+	 * 清理实现<br>
+	 * 子类实现此方法时无需加锁
+	 *
 	 * @return 清理数
 	 */
 	protected abstract int pruneCache();
 
 	@Override
 	public final int prune() {
-		writeLock.lock();
+		final long stamp = lock.writeLock();
 		try {
 			return pruneCache();
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 	}
 	// ---------------------------------------------------------------- prune end
@@ -235,7 +232,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 
 	/**
 	 * @return 默认缓存失效时长。<br>
-	 *         每个对象可以单独设置失效时长
+	 * 每个对象可以单独设置失效时长
 	 */
 	@Override
 	public long timeout() {
@@ -244,26 +241,16 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 
 	/**
 	 * 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用
-	 * 
+	 *
 	 * @return 过期对象清理是否可用,内部使用
 	 */
 	protected boolean isPruneExpiredActive() {
-		this.readLock.lock();
-		try {
-			return (timeout != 0) || existCustomTimeout;
-		} finally {
-			this.readLock.unlock();
-		}
+		return (timeout != 0) || existCustomTimeout;
 	}
 
 	@Override
 	public boolean isFull() {
-		this.readLock.lock();
-		try {
-			return (capacity > 0) && (cacheMap.size() >= capacity);
-		} finally {
-			this.readLock.unlock();
-		}
+		return (capacity > 0) && (cacheMap.size() >= capacity);
 	}
 
 	@Override
@@ -273,49 +260,34 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 
 	@Override
 	public void clear() {
-		writeLock.lock();
+		final long stamp = lock.writeLock();
 		try {
 			cacheMap.clear();
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 	}
 
 	@Override
 	public int size() {
-		this.readLock.lock();
-		try {
-			return cacheMap.size();
-		} finally {
-			this.readLock.unlock();
-		}
+		return cacheMap.size();
 	}
 
 	@Override
 	public boolean isEmpty() {
-		this.readLock.lock();
-		try {
-			return cacheMap.isEmpty();
-		} finally {
-			this.readLock.unlock();
-		}
+		return cacheMap.isEmpty();
 	}
 
 	@Override
 	public String toString() {
-		this.readLock.lock();
-		try {
-			return this.cacheMap.toString();
-		} finally {
-			this.readLock.unlock();
-		}
+		return this.cacheMap.toString();
 	}
 	// ---------------------------------------------------------------- common end
 
 	/**
 	 * 对象移除回调。默认无动作
-	 * 
-	 * @param key 键
+	 *
+	 * @param key          
 	 * @param cachedObject 被缓存的对象
 	 */
 	protected void onRemove(K key, V cachedObject) {
@@ -324,27 +296,27 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 
 	/**
 	 * 移除key对应的对象
-	 * 
-	 * @param key 键
+	 *
+	 * @param key           
 	 * @param withMissCount 是否计数丢失数
 	 */
 	private void remove(K key, boolean withMissCount) {
-		writeLock.lock();
+		final long stamp = lock.writeLock();
 		CacheObj<K, V> co;
 		try {
 			co = removeWithoutLock(key, withMissCount);
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 		if (null != co) {
 			onRemove(co.key, co.obj);
 		}
 	}
-	
+
 	/**
 	 * 移除key对应的对象,不加锁
-	 * 
-	 * @param key 键
+	 *
+	 * @param key           
 	 * @param withMissCount 是否计数丢失数
 	 * @return 移除的对象,无返回null
 	 */

+ 29 - 45
hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java

@@ -1,15 +1,12 @@
 package cn.hutool.cache.test;
 
-import java.util.Iterator;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
 import cn.hutool.cache.Cache;
 import cn.hutool.cache.impl.FIFOCache;
 import cn.hutool.cache.impl.LRUCache;
 import cn.hutool.core.lang.Console;
 import cn.hutool.core.thread.ThreadUtil;
+import org.junit.Ignore;
+import org.junit.Test;
 
 /**
  * 缓存单元测试
@@ -28,30 +25,22 @@ public class CacheConcurrentTest {
 		// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
 
 		for (int i = 0; i < threadCount; i++) {
-			ThreadUtil.execute(new Runnable() {
-				@Override
-				public void run() {
-					cache.put("key1", "value1", System.currentTimeMillis() * 3);
-					cache.put("key2", "value2", System.currentTimeMillis() * 3);
-					cache.put("key3", "value3", System.currentTimeMillis() * 3);
-					cache.put("key4", "value4", System.currentTimeMillis() * 3);
-					ThreadUtil.sleep(1000);
-					cache.put("key5", "value5", System.currentTimeMillis() * 3);
-					cache.put("key6", "value6", System.currentTimeMillis() * 3);
-					cache.put("key7", "value7", System.currentTimeMillis() * 3);
-					cache.put("key8", "value8", System.currentTimeMillis() * 3);
-					Console.log("put all");
-				}
+			ThreadUtil.execute(() -> {
+				cache.put("key1", "value1", System.currentTimeMillis() * 3);
+				cache.put("key2", "value2", System.currentTimeMillis() * 3);
+				cache.put("key3", "value3", System.currentTimeMillis() * 3);
+				cache.put("key4", "value4", System.currentTimeMillis() * 3);
+				ThreadUtil.sleep(1000);
+				cache.put("key5", "value5", System.currentTimeMillis() * 3);
+				cache.put("key6", "value6", System.currentTimeMillis() * 3);
+				cache.put("key7", "value7", System.currentTimeMillis() * 3);
+				cache.put("key8", "value8", System.currentTimeMillis() * 3);
+				Console.log("put all");
 			});
 		}
 
 		for (int i = 0; i < threadCount; i++) {
-			ThreadUtil.execute(new Runnable() {
-				@Override
-				public void run() {
-					show(cache);
-				}
-			});
+			ThreadUtil.execute(() -> show(cache));
 		}
 
 		System.out.println("==============================");
@@ -66,23 +55,20 @@ public class CacheConcurrentTest {
 
 		for (int i = 0; i < threadCount; i++) {
 			final int index = i;
-			ThreadUtil.execute(new Runnable() {
-				@Override
-				public void run() {
-					cache.put("key1"+ index, "value1");
-					cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
-					
-					int size = cache.size();
-					int capacity = cache.capacity();
-					if(size > capacity) {
-						Console.log("{} {}", size, capacity);
-					}
-					ThreadUtil.sleep(1000);
-					size = cache.size();
-					capacity = cache.capacity();
-					if(size > capacity) {
-						Console.log("## {} {}", size, capacity);
-					}
+			ThreadUtil.execute(() -> {
+				cache.put("key1"+ index, "value1");
+				cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
+
+				int size = cache.size();
+				int capacity = cache.capacity();
+				if(size > capacity) {
+					Console.log("{} {}", size, capacity);
+				}
+				ThreadUtil.sleep(1000);
+				size = cache.size();
+				capacity = cache.capacity();
+				if(size > capacity) {
+					Console.log("## {} {}", size, capacity);
 				}
 			});
 		}
@@ -91,10 +77,8 @@ public class CacheConcurrentTest {
 	}
 
 	private void show(Cache<String, String> cache) {
-		Iterator<?> its = cache.iterator();
 
-		while (its.hasNext()) {
-			Object tt = its.next();
+		for (Object tt : cache) {
 			Console.log(tt);
 		}
 	}

+ 1 - 1
hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java

@@ -58,7 +58,7 @@ public class CacheTest {
 		//使用时间推近
 		lruCache.get("key1");
 		lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
-		
+
 		String value1 = lruCache.get("key1");
 		Assert.assertNotNull(value1);
 		//由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除)

+ 38 - 28
hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java

@@ -1,13 +1,11 @@
 package cn.hutool.core.lang;
 
+import cn.hutool.core.lang.func.Func0;
+
 import java.io.Serializable;
 import java.util.Map;
 import java.util.WeakHashMap;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
-
-import cn.hutool.core.lang.func.Func0;
+import java.util.concurrent.locks.StampedLock;
 
 /**
  * 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理
@@ -21,10 +19,9 @@ public class SimpleCache<K, V> implements Serializable{
 	
 	/** 池 */
 	private final Map<K, V> cache = new WeakHashMap<>();
-	
-	private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
-	private final ReadLock readLock = cacheLock.readLock();
-	private final WriteLock writeLock = cacheLock.writeLock();
+
+	// 乐观读写锁
+	private final StampedLock lock = new StampedLock ();
 
 	/**
 	 * 从缓存池中查找值
@@ -33,15 +30,12 @@ public class SimpleCache<K, V> implements Serializable{
 	 * @return 值
 	 */
 	public V get(K key) {
-		// 尝试读取缓存
-		readLock.lock();
-		V value;
+		long stamp = lock.readLock();
 		try {
-			value = cache.get(key);
+			return cache.get(key);
 		} finally {
-			readLock.unlock();
+			lock.unlockRead(stamp);
 		}
-		return value;
 	}
 	
 	/**
@@ -52,12 +46,25 @@ public class SimpleCache<K, V> implements Serializable{
 	 * @return 值对象
 	 */
 	public V get(K key, Func0<V> supplier) {
-		V v = get(key);
-		if (null == v && null != supplier) {
-			writeLock.lock();
-			try {
-				// 双重检查锁
+		if(null == supplier){
+			return get(key);
+		}
+
+		long stamp = lock.readLock();
+		V v;
+		try{
+			v = cache.get(key);
+			if (null == v) {
+				// 尝试转换独占写锁
+				long writeStamp = lock.tryConvertToWriteLock(stamp);
+				if(0 == writeStamp){
+					// 转换失败,手动更新为写锁
+					lock.unlockRead(stamp);
+					writeStamp = lock.writeLock();
+				}
+				stamp = writeStamp;
 				v = cache.get(key);
+				// 双重检查,防止在竞争锁的过程中已经有其它线程写入
 				if(null == v) {
 					try {
 						v = supplier.call();
@@ -66,9 +73,9 @@ public class SimpleCache<K, V> implements Serializable{
 					}
 					cache.put(key, v);
 				}
-			} finally {
-				writeLock.unlock();
 			}
+		} finally {
+			lock.unlock(stamp);
 		}
 		return v;
 	}
@@ -80,11 +87,12 @@ public class SimpleCache<K, V> implements Serializable{
 	 * @return 值
 	 */
 	public V put(K key, V value){
-		writeLock.lock();
+		// 独占写锁
+		final long stamp = lock.writeLock();
 		try {
 			cache.put(key, value);
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 		return value;
 	}
@@ -96,11 +104,12 @@ public class SimpleCache<K, V> implements Serializable{
 	 * @return 移除的值
 	 */
 	public V remove(K key) {
-		writeLock.lock();
+		// 独占写锁
+		final long stamp = lock.writeLock();
 		try {
 			return cache.remove(key);
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 	}
 
@@ -108,11 +117,12 @@ public class SimpleCache<K, V> implements Serializable{
 	 * 清空缓存池
 	 */
 	public void clear() {
-		writeLock.lock();
+		// 独占写锁
+		final long stamp = lock.writeLock();
 		try {
 			this.cache.clear();
 		} finally {
-			writeLock.unlock();
+			lock.unlockWrite(stamp);
 		}
 	}
 }

+ 43 - 0
hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java

@@ -0,0 +1,43 @@
+package cn.hutool.core.thread.lock;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.StampedLock;
+
+/**
+ * 锁相关工具
+ *
+ * @author looly
+ * @since 5.2.5
+ */
+public class LockUtil {
+
+	private static NoLock NO_LOCK = new NoLock();
+
+	/**
+	 * 创建{@link StampedLock}锁
+	 *
+	 * @return {@link StampedLock}锁
+	 */
+	public static StampedLock createStampLock() {
+		return new StampedLock();
+	}
+
+	/**
+	 * 创建{@link ReentrantReadWriteLock}锁
+	 *
+	 * @param fair 是否公平锁
+	 * @return {@link ReentrantReadWriteLock}锁
+	 */
+	public static ReentrantReadWriteLock createReadWriteLock(boolean fair) {
+		return new ReentrantReadWriteLock(fair);
+	}
+
+	/**
+	 * 获取单例的无锁对象
+	 *
+	 * @return {@link NoLock}
+	 */
+	public static NoLock getNoLock(){
+		return NO_LOCK;
+	}
+}

+ 4 - 2
hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java

@@ -17,7 +17,7 @@ public class NoLock implements Lock{
 	}
 
 	@Override
-	public void lockInterruptibly() throws InterruptedException {
+	public void lockInterruptibly() {
 	}
 
 	@Override
@@ -25,8 +25,9 @@ public class NoLock implements Lock{
 		return true;
 	}
 
+	@SuppressWarnings("NullableProblems")
 	@Override
-	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
+	public boolean tryLock(long time, TimeUnit unit) {
 		return true;
 	}
 
@@ -34,6 +35,7 @@ public class NoLock implements Lock{
 	public void unlock() {
 	}
 
+	@SuppressWarnings("NullableProblems")
 	@Override
 	public Condition newCondition() {
 		return null;

+ 46 - 0
hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java

@@ -0,0 +1,46 @@
+package cn.hutool.core.lang;
+
+import cn.hutool.core.thread.ThreadUtil;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SimpleCacheTest {
+
+	@Before
+	public void putTest(){
+		final SimpleCache<String, String> cache = new SimpleCache<>();
+		ThreadUtil.execute(()->cache.put("key1", "value1"));
+		ThreadUtil.execute(()->cache.get("key1"));
+		ThreadUtil.execute(()->cache.put("key2", "value2"));
+		ThreadUtil.execute(()->cache.get("key2"));
+		ThreadUtil.execute(()->cache.put("key3", "value3"));
+		ThreadUtil.execute(()->cache.get("key3"));
+		ThreadUtil.execute(()->cache.put("key4", "value4"));
+		ThreadUtil.execute(()->cache.get("key4"));
+		ThreadUtil.execute(()->cache.get("key5", ()->"value5"));
+
+		cache.get("key5", ()->"value5");
+	}
+
+	@Test
+	public void getTest(){
+		final SimpleCache<String, String> cache = new SimpleCache<>();
+		cache.put("key1", "value1");
+		cache.get("key1");
+		cache.put("key2", "value2");
+		cache.get("key2");
+		cache.put("key3", "value3");
+		cache.get("key3");
+		cache.put("key4", "value4");
+		cache.get("key4");
+		cache.get("key5", ()->"value5");
+
+		Assert.assertEquals("value1", cache.get("key1"));
+		Assert.assertEquals("value2", cache.get("key2"));
+		Assert.assertEquals("value3", cache.get("key3"));
+		Assert.assertEquals("value4", cache.get("key4"));
+		Assert.assertEquals("value5", cache.get("key5"));
+		Assert.assertEquals("value6", cache.get("key6", ()-> "value6"));
+	}
+}

+ 29 - 3
hutool-script/pom.xml

@@ -1,9 +1,10 @@
 <?xml version='1.0' encoding='utf-8'?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 
 	<packaging>jar</packaging>
-	
+
 	<parent>
 		<groupId>cn.hutool</groupId>
 		<artifactId>hutool-parent</artifactId>
@@ -13,12 +14,37 @@
 	<artifactId>hutool-script</artifactId>
 	<name>${project.artifactId}</name>
 	<description>Hutool 脚本执行封装</description>
-	
+
+	<properties>
+		<jython.version>2.7.0</jython.version>
+		<luaj.version>3.0.1</luaj.version>
+		<groovy.version>3.0.2</groovy.version>
+	</properties>
+
 	<dependencies>
 		<dependency>
 			<groupId>cn.hutool</groupId>
 			<artifactId>hutool-core</artifactId>
 			<version>${project.parent.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>org.python</groupId>
+			<artifactId>jython</artifactId>
+			<version>${jython.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.luaj</groupId>
+			<artifactId>luaj-jse</artifactId>
+			<version>${luaj.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.groovy</groupId>
+			<artifactId>groovy-all</artifactId>
+			<version>${groovy.version}</version>
+			<type>pom</type>
+			<scope>provided</scope>
+		</dependency>
 	</dependencies>
 </project>

+ 14 - 15
hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java

@@ -1,65 +1,64 @@
 package cn.hutool.script;
 
-import java.io.Reader;
-
 import javax.script.Bindings;
 import javax.script.Compilable;
 import javax.script.CompiledScript;
 import javax.script.Invocable;
 import javax.script.ScriptContext;
 import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
+import java.io.Reader;
 
 /**
  * Javascript引擎类
- * @author Looly
  *
+ * @author Looly
  */
-public class JavaScriptEngine extends FullSupportScriptEngine{
-	
+public class JavaScriptEngine extends FullSupportScriptEngine {
+
 	public JavaScriptEngine() {
-		super(new ScriptEngineManager().getEngineByName("javascript"));
+		super(ScriptUtil.getJsEngine());
 	}
-	
+
 	/**
 	 * 引擎实例
+	 *
 	 * @return 引擎实例
 	 */
-	public static JavaScriptEngine instance(){
+	public static JavaScriptEngine instance() {
 		return new JavaScriptEngine();
 	}
 
 	//----------------------------------------------------------------------------------------------- Invocable
 	@Override
 	public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException {
-		return ((Invocable)engine).invokeMethod(thiz, name, args);
+		return ((Invocable) engine).invokeMethod(thiz, name, args);
 	}
 
 	@Override
 	public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
-		return ((Invocable)engine).invokeFunction(name, args);
+		return ((Invocable) engine).invokeFunction(name, args);
 	}
 
 	@Override
 	public <T> T getInterface(Class<T> clasz) {
-		return ((Invocable)engine).getInterface(clasz);
+		return ((Invocable) engine).getInterface(clasz);
 	}
 
 	@Override
 	public <T> T getInterface(Object thiz, Class<T> clasz) {
-		return ((Invocable)engine).getInterface(thiz, clasz);
+		return ((Invocable) engine).getInterface(thiz, clasz);
 	}
 
 	//----------------------------------------------------------------------------------------------- Compilable
 	@Override
 	public CompiledScript compile(String script) throws ScriptException {
-		return ((Compilable)engine).compile(script);
+		return ((Compilable) engine).compile(script);
 	}
 
 	@Override
 	public CompiledScript compile(Reader script) throws ScriptException {
-		return ((Compilable)engine).compile(script);
+		return ((Compilable) engine).compile(script);
 	}
 
 	//----------------------------------------------------------------------------------------------- ScriptEngine

+ 2 - 3
hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java

@@ -1,10 +1,10 @@
 package cn.hutool.script;
 
-import javax.script.ScriptException;
-
 import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.util.StrUtil;
 
+import javax.script.ScriptException;
+
 /**
  * 脚本运行时异常
  * 
@@ -50,7 +50,6 @@ public class ScriptRuntimeException extends RuntimeException {
 		super(message);
 		this.fileName = fileName;
 		this.lineNumber = lineNumber;
-		this.columnNumber = -1;
 	}
 
 	/**

+ 86 - 25
hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java

@@ -1,5 +1,8 @@
 package cn.hutool.script;
 
+import cn.hutool.core.lang.SimpleCache;
+import cn.hutool.core.util.StrUtil;
+
 import javax.script.Bindings;
 import javax.script.Compilable;
 import javax.script.CompiledScript;
@@ -10,34 +13,92 @@ import javax.script.ScriptException;
 
 /**
  * 脚本工具类
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class ScriptUtil {
 
+	private static final ScriptEngineManager manager = new ScriptEngineManager();
+	private static SimpleCache<String, ScriptEngine> cache = new SimpleCache<>();
+
 	/**
 	 * 获得 {@link ScriptEngine} 实例
-	 * 
-	 * @param name 脚本名称
+	 *
+	 * @param nameOrExtOrMime 脚本名称
 	 * @return {@link ScriptEngine} 实例
 	 */
-	public static ScriptEngine getScript(String name) {
-		return new ScriptEngineManager().getEngineByName(name);
+	public static ScriptEngine getScript(String nameOrExtOrMime) {
+		return cache.get(nameOrExtOrMime, ()->{
+			ScriptEngine engine = manager.getEngineByName(nameOrExtOrMime);
+			if (null == engine) {
+				engine = manager.getEngineByExtension(nameOrExtOrMime);
+			}
+			if (null == engine) {
+				engine = manager.getEngineByMimeType(nameOrExtOrMime);
+			}
+			if (null == engine) {
+				throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime));
+			}
+			return engine;
+		});
 	}
 
 	/**
 	 * 获得 Javascript引擎 {@link JavaScriptEngine}
-	 * 
+	 *
 	 * @return {@link JavaScriptEngine}
 	 */
 	public static JavaScriptEngine getJavaScriptEngine() {
 		return new JavaScriptEngine();
 	}
-	
+
 	/**
-	 * 编译脚本
-	 * 
+	 * 获得 JavaScript引擎
+	 *
+	 * @return Python引擎
+	 * @since 5.2.5
+	 */
+	public static ScriptEngine getJsEngine() {
+		return getScript("js");
+	}
+
+	/**
+	 * 获得 Python引擎<br>
+	 * 需要引入org.python:jython
+	 *
+	 * @return Python引擎
+	 * @since 5.2.5
+	 */
+	public static ScriptEngine getPythonEngine() {
+		System.setProperty("python.import.site", "false");
+		return getScript("python");
+	}
+
+	/**
+	 * 获得Lua引擎<br>
+	 * 需要引入org.luaj:luaj-jse
+	 *
+	 * @return Lua引擎
+	 * @since 5.2.5
+	 */
+	public static ScriptEngine getLuaEngine() {
+		return getScript("lua");
+	}
+
+	/**
+	 * 获得Groovy引擎<br>
+	 * 需要引入org.codehaus.groovy:groovy-all
+	 *
+	 * @return Groovy引擎
+	 * @since 5.2.5
+	 */
+	public static ScriptEngine getGroovyEngine() {
+		return getScript("groovy");
+	}
+
+	/**
+	 * 执行脚本
+	 *
 	 * @param script 脚本内容
 	 * @return {@link CompiledScript}
 	 * @throws ScriptRuntimeException 脚本异常
@@ -45,16 +106,16 @@ public class ScriptUtil {
 	 */
 	public static Object eval(String script) throws ScriptRuntimeException {
 		try {
-			return compile(script).eval();
+			return getJsEngine().eval(script);
 		} catch (ScriptException e) {
 			throw new ScriptRuntimeException(e);
 		}
 	}
-	
+
 	/**
-	 * 编译脚本
-	 * 
-	 * @param script 脚本内容
+	 * 执行脚本
+	 *
+	 * @param script  脚本内容
 	 * @param context 脚本上下文
 	 * @return {@link CompiledScript}
 	 * @throws ScriptRuntimeException 脚本异常
@@ -62,16 +123,16 @@ public class ScriptUtil {
 	 */
 	public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException {
 		try {
-			return compile(script).eval(context);
+			return getJsEngine().eval(script, context);
 		} catch (ScriptException e) {
 			throw new ScriptRuntimeException(e);
 		}
 	}
-	
+
 	/**
-	 * 编译脚本
-	 * 
-	 * @param script 脚本内容
+	 * 执行脚本
+	 *
+	 * @param script   脚本内容
 	 * @param bindings 绑定的参数
 	 * @return {@link CompiledScript}
 	 * @throws ScriptRuntimeException 脚本异常
@@ -79,7 +140,7 @@ public class ScriptUtil {
 	 */
 	public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException {
 		try {
-			return compile(script).eval(bindings);
+			return getJsEngine().eval(script, bindings);
 		} catch (ScriptException e) {
 			throw new ScriptRuntimeException(e);
 		}
@@ -87,7 +148,7 @@ public class ScriptUtil {
 
 	/**
 	 * 编译脚本
-	 * 
+	 *
 	 * @param script 脚本内容
 	 * @return {@link CompiledScript}
 	 * @throws ScriptRuntimeException 脚本异常
@@ -95,7 +156,7 @@ public class ScriptUtil {
 	 */
 	public static CompiledScript compile(String script) throws ScriptRuntimeException {
 		try {
-			return compile(getJavaScriptEngine(), script);
+			return compile(getJsEngine(), script);
 		} catch (ScriptException e) {
 			throw new ScriptRuntimeException(e);
 		}
@@ -103,7 +164,7 @@ public class ScriptUtil {
 
 	/**
 	 * 编译脚本
-	 * 
+	 *
 	 * @param engine 引擎
 	 * @param script 脚本内容
 	 * @return {@link CompiledScript}
@@ -111,7 +172,7 @@ public class ScriptUtil {
 	 */
 	public static CompiledScript compile(ScriptEngine engine, String script) throws ScriptException {
 		if (engine instanceof Compilable) {
-			Compilable compEngine = (Compilable) engine;
+			final Compilable compEngine = (Compilable) engine;
 			return compEngine.compile(script);
 		}
 		return null;

+ 23 - 5
hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java

@@ -1,12 +1,12 @@
 package cn.hutool.script.test;
 
-import javax.script.CompiledScript;
-import javax.script.ScriptException;
-
-import org.junit.Test;
-
 import cn.hutool.script.ScriptRuntimeException;
 import cn.hutool.script.ScriptUtil;
+import org.junit.Test;
+
+import javax.script.CompiledScript;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
 
 /**
  * 脚本单元测试类
@@ -30,4 +30,22 @@ public class ScriptUtilTest {
 	public void evalTest() {
 		ScriptUtil.eval("print('Script test!');");
 	}
+
+	@Test
+	public void pythonTest() throws ScriptException {
+		final ScriptEngine pythonEngine = ScriptUtil.getPythonEngine();
+		pythonEngine.eval("print('Hello Python')");
+	}
+
+	@Test
+	public void luaTest() throws ScriptException {
+		final ScriptEngine engine = ScriptUtil.getLuaEngine();
+		engine.eval("print('Hello Lua')");
+	}
+
+	@Test
+	public void groovyTest() throws ScriptException {
+		final ScriptEngine engine = ScriptUtil.getGroovyEngine();
+		engine.eval("println 'Hello Groovy'");
+	}
 }