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.MiscTests; 030import org.apache.hadoop.hbase.testclassification.SmallTests; 031import org.junit.jupiter.api.Tag; 032import org.junit.jupiter.api.Test; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036@Tag(MiscTests.TAG) 037@Tag(SmallTests.TAG) 038public class TestFastStringPool { 039 040 private static final Logger LOG = LoggerFactory.getLogger(TestFastStringPool.class); 041 042 @Test 043 public void testMultiThread() throws InterruptedException { 044 FastStringPool pool = new FastStringPool(); 045 List<String> list1 = new ArrayList<>(); 046 List<String> list2 = new ArrayList<>(); 047 for (int i = 0; i < 1000; i++) { 048 list1.add("list1-" + i); 049 list2.add("list2-" + i); 050 } 051 Map<String, String> interned1 = new HashMap<>(); 052 Map<String, String> interned2 = new HashMap<>(); 053 AtomicBoolean failed = new AtomicBoolean(false); 054 int numThreads = 10; 055 List<Thread> threads = new ArrayList<>(); 056 for (int i = 0; i < numThreads; i++) { 057 threads.add(new Thread(() -> { 058 ThreadLocalRandom rand = ThreadLocalRandom.current(); 059 for (int j = 0; j < 1000000; j++) { 060 List<String> list; 061 Map<String, String> interned; 062 if (rand.nextBoolean()) { 063 list = list1; 064 interned = interned1; 065 } else { 066 list = list2; 067 interned = interned2; 068 } 069 // create a new reference 070 String k = new String(list.get(rand.nextInt(list.size()))); 071 String v = pool.intern(k); 072 synchronized (interned) { 073 String prev = interned.get(k); 074 if (prev != null) { 075 // should always return the same reference 076 if (prev != v) { 077 failed.set(true); 078 String msg = "reference not equal, intern failed on string " + k; 079 LOG.error(msg); 080 throw new AssertionError(msg); 081 } 082 } else { 083 interned.put(k, v); 084 } 085 } 086 } 087 })); 088 } 089 for (Thread t : threads) { 090 t.start(); 091 } 092 for (Thread t : threads) { 093 t.join(); 094 } 095 LOG.info("interned1 size {}, interned2 size {}, pool size {}", interned1.size(), 096 interned2.size(), pool.size()); 097 assertEquals(interned1.size() + interned2.size(), pool.size()); 098 interned1.clear(); 099 list1.clear(); 100 LOG.info("clear interned1"); 101 // wait for at most 30 times 102 for (int i = 0; i < 30; i++) { 103 // invoke gc manually 104 LOG.info("trigger GC"); 105 System.gc(); 106 Thread.sleep(1000); 107 // should have cleaned up all the references for list1 108 if (interned2.size() == pool.size()) { 109 return; 110 } 111 } 112 fail("should only have list2 strings in pool, expected pool size " + interned2.size() 113 + ", but got " + pool.size()); 114 } 115}