今天有一个任务:向数据库中添加百万条数据用来做测试,考虑到单线程插入数据可能要花很久的时间,于是想到了用线程池来进行插入操作,但是里面有个一个唯一性约束字段,然后在这个过程中遇到了好几个问题:

  1. 一开始使用线程池的时候由于唯一性约束字段的存在,需要将一个公有变量变成私有变量(每个线程在执行过程中变量需要不被外界修改),但是线程池中的线程是通过实现Runnable接口方式创建的,那么类中的私有变量其实是公有变量。
  2. 了解到ThreadLocal的用法后,也踩了几次坑,这里总结一些ThreadLocal的用法:尽量使用private static声明ThreadLocal变量;出main线程外,只有在run()中才能调用到TheadLocal变量(run中调用的其他方法中也能调用到,一开始将其放在在MyThread类的构造方法中,一直有NPE报出),说明只有在run()中,这个线程才算真正有了自己的生命周期。
  3. 线程数和循环次数不要太狠,尤其是在添加了对象到List中的时候,很容易出现内存不足异常。
  4. ThreadLocal变量在不需要当前存放的值的时候,必须使用remove方法清除,这点也是一个大坑,下面主要是介绍这个坑以及解决方法。
  5. 调用线程池的shutdown()方法意味着该线程池在线程池中的所有线程执行完毕后就“关机了”,将不再接受任何调用。

(1)在线程中执行添加一段连续的Integer到List中;(2)(1)中的Integer不能是重复的;(3)在main中向线程池中添加10条线程,并启动它们;(4)线程池中的每条线程循环执行10次;未使用remove()的代码(其实也就是我注释了remove)如下:

public class ThreadLocalTest {
    static Integer initNum = 1; //初始数据
    static int step = 10; //步长
    static int threads = 10; //线程数
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();
        ExecutorService executorService = Executors.newFixedThreadPool(threads);
        List<MyThread> list = new ArrayList<>();
        for(int i = 0; i < threads; i++) {
            list.add(threadLocalTest.new MyThread());
        }
        for(int j = 0; j < 10; j++) {
            for (int i = 0; i < threads; i++) {
                executorService.execute(list.get(i));
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }

    class MyThread implements Runnable{
        @Override
        public void run() {
            if(null == threadLocal.get()){
                synchronized (MyThread.class){
                    threadLocal.set(initNum);
                    initNum+=step;
                }
            }
            System.out.println(">>>>"+Thread.currentThread().getName()+">> 开始");
            List list = new ArrayList<Integer>();
            for(int i = 0; i < step; i++) {
                int localNum = threadLocal.get();
                list.add(localNum);
                threadLocal.set(++localNum);
            }
//            threadLocal.remove();
            System.out.println(">>>>"+Thread.currentThread().getName()+">> 完成!---- "+list.toString());
        }
    }
}

此时会出现如下结果:

根据上面的结果可以看出,这并不是我想要的结果,按照我的需要最后结果应该是1000,而此时只是到了200,而且其中有重复的数字。

然后我感觉可能是我没有添加我注释的threadLocal.remove(),加上后,果然,结果如我所预料的:

最后根据上面代码的思路,只花了大概6-7分钟完成了对mysql数据库插入100W条数据操作,美滋滋~