今天,我终于写了我大学时候想写的代码了
前言
最近心情不是很好,然后就开始回忆以前大学的时光,好想和大学的朋友再吃一顿饭啊。
然后想到一个月黑风高的晚上,我的宿友和我讨论多线程的问题:
“假如有一个非常大的文件,要入库,你怎么将这个文件读取入库?”
当时我说:“很简单啊,就这样读文件然后入库啊。”
他说:“我当时也是这样说的,然后面试官叫我回去等通知。”
想想还是太菜了,然后他和我说可以用多线程去操作。我当时由于技术原因不知道怎么操作,只知道大概思路。而今天也是有空去实现当时的遗憾了。
思路
思路也挺简单的,就是多条线程共同操作一个变量,这个变量记录了这个大文件读取的进度,也可以理解为文件行数。各个线程读取一定的数据然后记录mark,分段入库。
代码
餐前甜品
此处模拟从数据库读取大量数据原理和分批插入相同,每条线程循环500次,偏移量为500条,使用AtomicInteger来进行线程变量共享。
public class myThread implements Runnable {
private volatile int num = 0;
private AtomicInteger val = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 500; i++) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("select id,name from user limit ");
stringBuffer.append(val.get());
val.addAndGet(500);
stringBuffer.append(",").append(val.get());
System.out.println(stringBuffer);
}
}
}
public static void main(String[] args) {
myThread myRunnable1 = new myThread();
Thread thread1= new Thread(myRunnable1);
Thread thread2 = new Thread(myRunnable1);
thread1.start();
thread2.start();
}
每条线程是500x500,也就是250000,此处开了2条线程,最终结果应该是500000。控制台输出如下
ok,不错,证明方向是正确的,接下来是模拟读取大文件入库。
正餐
线程类
@Component
public class myNewThread extends Thread {
@Autowired
private IUserService userService;
private static AtomicInteger val = new AtomicInteger();
public myNewThread(){
}
public void run() {
List<User> list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
User user = User.builder().wxName(val.get() + "").build();
SoftReference<User> stringBufferSoftReference = new SoftReference<>(user);
list.add(stringBufferSoftReference.get());
val.incrementAndGet();
}
userService.saveBatch(list);
}
}
@Test
void contextLoads() throws InterruptedException {
Thread thread3 = new myNewThread();
Thread thread4 = new myNewThread();
thread3.start();
thread4.start();
}
每条线程插入400条数据,wxName这个字段记录第几条数据
启动后发现控制台什么都没输出,数据库也没插入数据,很纳闷。然后经过一番思考后找出来问题所在。
在线程启动后加入以下两行代码
thread3.join();
thread4.join();
原因是,主线程在创建这两个线程后就结束了,子线程还没来得及操作数据库主线程就已经死亡了导致子线程被迫停止。
join表示主等待这线程执行结束,这样子就不会出现主线程创建完子线程就死亡导致子线程都没来得及执行线程体就死了的情况。
ok!启动!!
坏啦,空指针异常
经过调试发现,是service类在线程里面为空,导致能空指针异常,看来是spring捕获不到线程体。
既然捕获不到那我就传一个给线程体吧。
修改后的线程体
@Component
public class myNewThread extends Thread {
private IUserService userService;
private static AtomicInteger val = new AtomicInteger();
public myNewThread(IUserService userService){
this.userService = userService;
}
public myNewThread(){
}
public void run() {
List<User> list = new ArrayList<>();
for (int i = 0; i < 400; i++) {
User user = User.builder().wxName(val.get() + "").build();
SoftReference<User> stringBufferSoftReference = new SoftReference<>(user);
list.add(stringBufferSoftReference.get());
val.incrementAndGet();
}
userService.saveBatch(list);
}
}
修改后的单元测试
@Autowired
private IUserService userService;
@Test
void contextLoads() throws InterruptedException {
Thread thread3 = new myNewThread(userService);
//Thread thread4 = new myNewThread();
Thread thread4 = new myNewThread(userService);
thread3.start();
thread4.start();
thread3.join();
thread4.join();
}
简单来说就是使用构造函数给线程体传递一个非空的service类。
启动单元测试
证明是可以的刚刚好800条数据
总结
启动线程时要注意主线程和子线程的关系,然后操作数据库时,要注意传入的类是否空指针。
分段插入就到这里了,在高并发的情况下还没测试过,明天再说吧,已经是下午5点58了,下班!!!
链接:https://juejin.cn/post/7224051399255703613
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。