场景:利用SimpleDateFormat
格式化时间,因为SimpleDateFormat
线程不安全,每次都new一个优点浪费,想着每个线程内部维持1个SimpleDateFormat
,然而自定义Pet是可以做到的,SimpleDateFormat
做不到,好奇怪啊
摘要:Thread有个成员变量 ThreadLocalMap ,key为ThreadLocal,只要拿到key就能操作线程的 ThreadLocalMap,要保证是同一个ThreadLocal
对象,否则获取不到,怎么保证是同一个,那就只能把ThreadLocal存起来了,另外注意ThreadLocal用完之后记得remove
耗时:1天
private static void extracted4() throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 第一个坑:多个线程操作一个map,应该用ConcurrentHashMap
Map<String, ThreadLocal<Pet>> threadLocalMaps = new HashMap<>();
Map<String, List<Pet>> statistics = new HashMap<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
threadPool.submit(() -> {
String threadName = Thread.currentThread().getName();
ThreadLocal<Pet> threadLocal = threadLocalMaps.get(threadName);
if (threadLocal == null) {
/**
* 新建一个tl 保证每个线程里面都有一个自己的tl,
* Thread有个成员变量 ThreadLocalMap ,key为ThreadLocal,只要拿到key就能操作线程的 ThreadLocalMap
*/
// 第二个坑:并发问题
threadLocal = ThreadLocal.withInitial(Pet::new);
threadLocalMaps.put(threadName, threadLocal);
}
Pet sdf = threadLocal.get();
List<Pet> pets = statistics.computeIfAbsent(threadName, k -> new ArrayList<>());
pets.add(sdf);
countDownLatch.countDown();
});
}
// 坑3:main线程没有等待所有任务跑完
countDownLatch.await();
// 防止内存泄露,项目里面的线程池运行期间一般不会shutdown的
for (ThreadLocal<Pet> threadLocal4remove : threadLocalMaps.values()) {
threadLocal4remove.remove();
}
threadPool.shutdown();
// 结果统计 一个线程里面只有一个Pet
int count = 0;
for (Map.Entry<String, List<Pet>> entry : statistics.entrySet()) {
String key = entry.getKey();
List<Pet> pets = entry.getValue();
int size = pets.size();
count += size;
System.out.println(key + "\t" + size + "\t" + new HashSet<>(pets).size());
}
System.out.println(count);
}
pool-1-thread-1 102 2(预期是1,坑2导致的)
pool-1-thread-3 81 1
pool-1-thread-2 94 1
pool-1-thread-5 119 1
pool-1-thread-4 72 2
pool-1-thread-7 81 1
pool-1-thread-6 80 2
pool-1-thread-9 181 1
pool-1-thread-10 100 1
pool-1-thread-8 89 1
999(坑1导致的)
暂且就当无坑版本吧
private static void extracted4() throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
Map<String, ThreadLocal<Pet>> threadLocalMaps = new HashMap<>();
// 缺陷1.多个线程操作一个map,应该用ConcurrentHashMap
Map<String, List<Pet>> statistics = new ConcurrentHashMap<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
Object lock = new Object();
for (int i = 0; i < 1000; i++) {
threadPool.submit(() -> {
String threadName = Thread.currentThread().getName();
ThreadLocal<Pet> threadLocal = threadLocalMaps.get(threadName);
/**
* 新建一个tl 保证每个线程里面都有一个自己的tl,
* Thread有个成员变量 ThreadLocalMap ,key为ThreadLocal,只要拿到key就能操作线程的 ThreadLocalMap
* 注意:双重检查
*/
if (threadLocal == null) {
synchronized (lock) {
threadLocal = threadLocalMaps.get(threadName);
if (threadLocal == null) {
threadLocal = ThreadLocal.withInitial(Pet::new);
threadLocalMaps.put(threadName, threadLocal);
}
}
}
Pet sdf = threadLocal.get();
List<Pet> pets = statistics.computeIfAbsent(threadName, k -> new ArrayList<>());
pets.add(sdf);
countDownLatch.countDown();
});
}
countDownLatch.await();
// 防止内存泄露,项目里面的线程池运行期间一般不会shutdown的
for (ThreadLocal<Pet> threadLocal4remove : threadLocalMaps.values()) {
threadLocal4remove.remove();
}
threadPool.shutdown();
// 结果统计 一个线程里面只有一个Pet
int count = 0;
HashSet<Pet> sdfSet = new HashSet<>();
for (Map.Entry<String, List<Pet>> entry : statistics.entrySet()) {
String key = entry.getKey();
List<Pet> pets = entry.getValue();
sdfSet.addAll(pets);
int size = pets.size();
count += size;
System.out.println(key + "\t" + size + "\t" + new HashSet<>(pets).size());
}
System.out.println(count);
System.out.println(sdfSet.size());
}
pool-1-thread-1 95 1
pool-1-thread-3 103 1
pool-1-thread-2 105 1
pool-1-thread-5 87 1
pool-1-thread-4 108 1
pool-1-thread-7 104 1
pool-1-thread-6 107 1
pool-1-thread-10 123 1
pool-1-thread-9 99 1
pool-1-thread-8 69 1
1000
10
通用版本
private static void extractedX(Supplier<Object> supplier) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
Map<String, ThreadLocal<Object>> threadLocalMaps = new HashMap<>();
// 缺陷1.多个线程操作一个map,应该用ConcurrentHashMap
Map<String, List<Object>> statistics = new ConcurrentHashMap<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
Object lock = new Object();
for (int i = 0; i < 1000; i++) {
threadPool.submit(() -> {
String threadName = Thread.currentThread().getName();
ThreadLocal<Object> threadLocal = threadLocalMaps.get(threadName);
/*
* 新建一个tl 保证每个线程里面都有一个自己的tl,
* Thread有个成员变量 ThreadLocalMap ,key为ThreadLocal,只要拿到key就能操作线程的 ThreadLocalMap
* 注意:双重检查
*/
if (threadLocal == null) {
synchronized (lock) {
threadLocal = threadLocalMaps.get(threadName);
if (threadLocal == null) {
threadLocal = ThreadLocal.withInitial(supplier);
threadLocalMaps.put(threadName, threadLocal);
}
}
}
Object sdf = threadLocal.get();
List<Object> pets = statistics.computeIfAbsent(threadName, k -> new ArrayList<>());
pets.add(sdf);
countDownLatch.countDown();
});
}
countDownLatch.await();
// 防止内存泄露,项目里面的线程池运行期间一般不会shutdown的
for (ThreadLocal<Object> threadLocal4remove : threadLocalMaps.values()) {
threadLocal4remove.remove();
}
threadPool.shutdown();
// 结果统计 一个线程里面只有一个Pet
int count = 0;
HashSet<Object> sdfSet = new HashSet<>();
for (Map.Entry<String, List<Object>> entry : statistics.entrySet()) {
String key = entry.getKey();
List<Object> pets = entry.getValue();
sdfSet.addAll(pets);
int size = pets.size();
count += size;
System.out.println(key + "\t" + size + "\t" + new HashSet<>(pets).size());
}
System.out.println(count);
System.out.println(sdfSet.size());
}
SimpleDateFormat
哪里特殊吗???
public static void main(String[] args) throws InterruptedException {
extractedX(Pet::new);
extractedX(()->new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
pool-1-thread-1 87 1
pool-1-thread-3 124 1
pool-1-thread-2 91 1
pool-1-thread-5 137 1
pool-1-thread-4 94 1
pool-1-thread-7 75 1
pool-1-thread-6 112 1
pool-1-thread-9 89 1
pool-1-thread-10 78 1
pool-1-thread-8 113 1
1000
10
pool-2-thread-9 121 1
pool-2-thread-10 95 1
pool-2-thread-8 146 1
pool-2-thread-7 98 1
pool-2-thread-2 102 1
pool-2-thread-1 84 1
pool-2-thread-6 84 1
pool-2-thread-5 99 1
pool-2-thread-4 68 1
pool-2-thread-3 103 1
1000
1(预期的是10或者接近10,这个1是什么鬼???)
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("sdf1 = " + sdf1);
System.out.println("hashCode1 = " + sdf1.hashCode());
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("sdf2 = " + sdf2);
System.out.println("hashCode2 = " + sdf2.hashCode());
System.out.println(sdf1 == sdf2);
System.out.println(sdf1.equals(sdf2));