001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.util; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.fail; 022 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.ThreadLocalRandom; 028import java.util.concurrent.atomic.AtomicBoolean; 029import org.apache.hadoop.hbase.testclassification.SmallTests; 030import org.junit.jupiter.api.Tag; 031import org.junit.jupiter.api.Test; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035@Tag(SmallTests.TAG) 036public class TestFastStringPool { 037 038 private static final Logger LOG = LoggerFactory.getLogger(TestFastStringPool.class); 039 040 @Test 041 public void testMultiThread() throws InterruptedException { 042 FastStringPool pool = new FastStringPool(); 043 List<String> list1 = new ArrayList<>(); 044 List<String> list2 = new ArrayList<>(); 045 for (int i = 0; i < 1000; i++) { 046 list1.add("list1-" + i); 047 list2.add("list2-" + i); 048 } 049 Map<String, String> interned1 = new HashMap<>(); 050 Map<String, String> interned2 = new HashMap<>(); 051 AtomicBoolean failed = new AtomicBoolean(false); 052 int numThreads = 10; 053 List<Thread> threads = new ArrayList<>(); 054 for (int i = 0; i < numThreads; i++) { 055 threads.add(new Thread(() -> { 056 ThreadLocalRandom rand = ThreadLocalRandom.current(); 057 for (int j = 0; j < 1000000; j++) { 058 List<String> list; 059 Map<String, String> interned; 060 if (rand.nextBoolean()) { 061 list = list1; 062 interned = interned1; 063 } else { 064 list = list2; 065 interned = interned2; 066 } 067 // create a new reference 068 String k = new String(list.get(rand.nextInt(list.size()))); 069 String v = pool.intern(k); 070 synchronized (interned) { 071 String prev = interned.get(k); 072 if (prev != null) { 073 // should always return the same reference 074 if (prev != v) { 075 failed.set(true); 076 String msg = "reference not equal, intern failed on string " + k; 077 LOG.error(msg); 078 throw new AssertionError(msg); 079 } 080 } else { 081 interned.put(k, v); 082 } 083 } 084 } 085 })); 086 } 087 for (Thread t : threads) { 088 t.start(); 089 } 090 for (Thread t : threads) { 091 t.join(); 092 } 093 LOG.info("interned1 size {}, interned2 size {}, pool size {}", interned1.size(), 094 interned2.size(), pool.size()); 095 assertEquals(interned1.size() + interned2.size(), pool.size()); 096 interned1.clear(); 097 list1.clear(); 098 LOG.info("clear interned1"); 099 // wait for at most 30 times 100 for (int i = 0; i < 30; i++) { 101 // invoke gc manually 102 LOG.info("trigger GC"); 103 System.gc(); 104 Thread.sleep(1000); 105 // should have cleaned up all the references for list1 106 if (interned2.size() == pool.size()) { 107 return; 108 } 109 } 110 fail("should only have list2 strings in pool, expected pool size " + interned2.size() 111 + ", but got " + pool.size()); 112 } 113}