昨天写的文件扫描是单线程的,现在要使用多线程。
1、为什么要使用多线程?
为了提升扫描速度、为了学习多线程
2、Everything工具
只适合NTFS文件系统。文件有添加/修改/更新的时候,有记录日志,所以采用了扫描日志,而不是扫描树的方式进行。
如何通知主线程所有的任务已经完成?
一种解法:
构造任务的时候->传入带扫描文件夹->把它标记为dir
任务:
1、dir扫描出来——只需要扫描dir的孩子即可
2、扫描出所有的孩子中,还是文件夹的,封装成任务提交给线程池
哪个任务是最后一个?
每个人物都可能是最后一个完成的任务。
加入了打卡后可能会导致线程安全问题吗?
多线程安全问题:
public class Main {
private static final int COUNT = 100000000;
private static AtomicInteger ai = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
long b = System.currentTimeMillis();
Thread adder = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
ai.getAndIncrement(); //+1
}
});
adder.start();
Thread suber = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
ai.getAndDecrement(); //-1
}
});
suber.start();
adder.join();
suber.join();
System.out.println(ai.get());
long e = System.currentTimeMillis();
System.out.println(e - b);
}
}
用时不到2秒,
public class UseLock {
private static final int COUNT = 100000000;
private static int ai = 0;
public static void main(String[] args) throws InterruptedException {
long b =System.currentTimeMillis();
Thread adder = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
synchronized (UseLock.class) {
ai++;
}
}
});
adder.start();
Thread suber = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
synchronized (UseLock.class) {
ai--;
}
}
});
suber.start();
adder.join();
suber.join();
System.out.println(ai);
long e = System.currentTimeMillis();
System.out.println(e-b);
}
}
用时7秒。
最后一个完成任务的线程如何通知主线程 苏醒
1、主线程wait 最后一个完成任务的线程 notify
2、一个新的类:CountDownLatch
示例(比较复杂/会用就行):
public class Main {
public static void main(String[] args) throws InterruptedException {
final int N = 100;
final long[][] arrays = new long[N][]; //n个数组
Random r = new Random();
for (int i = 0; i < N; i++) {
arrays[i] = new long[100000];
for (int j = 0; j < 100000; j++) {
arrays[i][j] = r.nextInt();
}
}
//数组初始化数据
CountDownLatch startSignal = new CountDownLatch(1); //开始信号
CountDownLatch doneSignal = new CountDownLatch(N); //结束信号
Thread[] workers = new Thread[N];
for (int i = 0; i < N; i++) {
final long[] targetArray = arrays[i];
Thread worker = new Thread(() -> {
try {
//做一些准备工作
//还没有收到开始信号,等待
startSignal.await();
//收到开始信号,开始执行任务
Random random = new Random();
TimeUnit.SECONDS.sleep(random.nextInt(10) + 10);
Arrays.sort(targetArray);
Thread thread = Thread.currentThread();//返回当前线程对象的引用
System.out.printf("%s:以经完成任务\n",thread.getName());
//任务执行完成,还没有汇报
doneSignal.countDown();
//任务执行完成,并且已经汇报
} catch (InterruptedException e) {
//有人让该线程中断
e.printStackTrace();
}
},String.format("工作线程-%02d",i));
worker.start();
workers[i] = worker;
}
//特工已经埋伏下去了,没有开始信号,特工不会开始执行任务
//所以可以做一些准备工作
startSignal.countDown(); //发出开始指令,表示各个特工可以开始执行任务了
//能不能说,每个特工已经开始执行任务了?——不能
TimeUnit.SECONDS.sleep(10); //这个期间可以做其他工作
//等待所有特工都完成任务后,进行情况汇总
doneSignal.await(); //一共有N个信号,所以,只有N个结束信号都收到时,表示所有任务都完成了
//肯定所有任务都完成了
//进行汇总工作
for (int i = 0; i < N; i++) {
//数组肯定都排好序了
//肯定没有报错
if (arrays[i][0] > arrays[i][1000000-1]) {
System.out.println("错误");
}
}
}
}
所以选用CountDownLatch。
package File_scan;
import java.io.File;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 联想
* Date: 2021-01-20
* Time: 15:03
*/
public class Main {
private static ExecutorService threadPool = new ThreadPoolExecutor(
10,10,
0, TimeUnit.SECONDS,
new LinkedBlockingDeque<>()
);
private static void scanDirectory(File root) throws InterruptedException {
//打卡器 counter = new 打卡器();
AtomicInteger counter = new AtomicInteger(0);
CountDownLatch doneSignal = new CountDownLatch(1);
ScanTask task = new ScanTask(root,threadPool,counter,doneSignal);
//打卡
counter.incrementAndGet();
threadPool.execute(task);
//TODO:想办法,在这里等,直到root整棵树扫描完成
doneSignal.await();
threadPool.shutdown();
}
public static void main(String[] args) throws InterruptedException {
File root = new File("E:\\zhibo");
scanDirectory(root);
System.out.println("root整棵树 下的所有文件全部扫描完成");
}
}
package File_scan;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 联想
* Date: 2021-01-20
* Time: 15:14
*/
public class ScanTask implements Runnable{
private final File directory;
private final ExecutorService threadPool;
private final AtomicInteger counter;
private final CountDownLatch doneSignal;
public ScanTask(File directory, ExecutorService threadPool, AtomicInteger counter,CountDownLatch doneSignal) {
this.directory = directory;
this.threadPool = threadPool;
this.counter = counter;
this.doneSignal = doneSignal;
}
@Override
public void run() {
//打卡
//counter.incrementAndGet();
System.out.println(directory);
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
System.out.println(file);
if (file.isDirectory()) {
ScanTask task = new ScanTask(file,threadPool,counter,doneSignal);
//打卡
counter.incrementAndGet();
//提交给线程池
threadPool.execute(task);
}
}
}
//销卡
if (counter.decrementAndGet() == 0) {
//代表我是最后一个任务
doneSignal.countDown(); //发送一个结束信号
}
}
}
这就是多线程扫描文件夹。
亮点:
1、多线程编程:那些环节可能出现线程问题?发现问题如何修复
2、Atomic类(原子类)
3、CountDownLatch
4、多线程可以提升速度,理论+实践