我有一个我经常调用的实用程序方法(=static
方法),它使用java.util.regex.Matcher
。由于传递的正则表达式经常被重用,所以我尽量不每次都编译它,所以我将它保存在Map
中,其中键是正则表达式,值是Matcher
对象的List
(这样每个线程都可以获得自己的Matcher
实例)。
下面的代码片段是如何两次返回相同的Matcher
的。。。有时
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MyTest {
private static final Map<String, Queue<Matcher>> matchers = new HashMap<String, Queue<Matcher>>();
private static Set<Integer> duplicateHunter = new HashSet<Integer>();
private static Matcher getMatcher(String regexp, String value) {
Queue<Matcher> matcherQueue = matchers.get(regexp);
if (matcherQueue == null) {
synchronized (matchers) {
matcherQueue = matchers.get(regexp);
if (matcherQueue == null) {
// Create a new thread-safe Queue and a new Matcher
matcherQueue = new ConcurrentLinkedQueue<Matcher>();
matchers.put(regexp, matcherQueue);
} // Else: another thread already did what needed to be done
}
}
// Try to retrieve a Matcher
Matcher matcher = matcherQueue.poll();
if (matcher == null) {
// No matchers available, create one
// No lock needed, as it's not a drama to have a few more matchers in the queue
Pattern pattern = Pattern.compile(regexp);
matcher = pattern.matcher(value);
matcherQueue.offer(matcher);
} else {
// reset the matcher
matcher.reset(value);
}
// boolean notADuplicate = duplicateHunter.add(matcher.hashCode());
// if(!notADuplicate) {
// throw new RuntimeException("DUPLICATE!!!");
// }
return matcher;
}
private static void returnMatcher(String regex, Matcher matcher) {
Queue<Matcher> matcherQueue = matchers.get(regex);
//duplicateHunter.remove(matcher.hashCode());
matcherQueue.offer(matcher);
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 50000; i++) {
String regex = ".*";
Matcher matcher = null;
try {
matcher = getMatcher(regex, "toto" + i);
if (matcher.matches()) {
// matches
}
} finally {
if (matcher != null) {
returnMatcher(regex, matcher);
}
}
}
}
});
thread.start();
}
}
}
您将得到一个"java.lang.StringIndexOutOfBoundsException
:字符串索引超出范围"。启用duplicateHunter
代码,您会看到Matcher
有时确实会返回两次。
(没有显示static
实用程序方法,main
方法是为了向您展示问题而制作的)
当regexp没有匹配器时,您可以创建一个新的匹配器,但也可以立即将其添加到队列中:
if (matcher == null) {
// No matchers available, create one
// No lock needed, as it's not a drama to have a few more matchers in the queue
Pattern pattern = Pattern.compile(regexp);
matcher = pattern.matcher(value);
matcherQueue.offer(matcher); // Don't add it to the queue here!
}
因此,当您使用它时,它将在队列中,并且在您完成之前,另一个线程可以很容易地获得它。
顺便说一句,我不知道我是否同意你把火柴人集中在一起的想法。就CPU周期而言,它们的创建成本并不高。你可能想对它进行评测,看看它是否值得。不过,预编译Pattern
是个好主意。
当您创建一个新的Matcher时,您会在返回它之前将它提供给Queue,这样下一个线程就会立即获得它。
matcher = pattern.matcher(value);
matcherQueue.offer(matcher); // <-- this line should be taken taken out and shot
...
return matcher;
此外,您的duplicateHunter哈希集不是线程安全的,在对其进行验证时可能会给您错误的结果。