使用多线程踩一个“坑”——使用多线程真的会使用CPU所有的内核吗
学习多线程的时候,我们都知道如果多个线程分配到CPU多个内核是可以并发的执行。但真的是这样的吗?
先来看看电脑配置:
电脑配置测试电脑是单CPU,4核。按道理来说创建4个线程应该可以分配到4个内核同时执行。接下来执行测试代码看结果!
public class ThreadTest {
private static final int num = 1000 * 1000;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
for (int i = 0; i < num; i++) {
System.out.println(i);
}
},"线程1").start();
new Thread(()->{
for (int i = 0; i < num; i++) {
System.out.println(i);
}
},"线程2").start();
new Thread(()->{
for (int i = 0; i < num; i++) {
System.out.println(i);
}
},"线程3").start();
new Thread(()->{
for (int i = 0; i < num; i++) {
System.out.println(i);
}
},"线程4").start();
}
}
测试代码创建了四个线程,四个线程都遍历一百万次。通过使用JDK自带监控工具:Visual VM 查看线程的执行过程,是不是真的如我想象,并发的执行线程呢?
Visual VM线程监控截图关注红色框的内容,惊奇的发现,多个线程根本没有并发执行,而是不断的在线程之间上下文切换!也就是说,4个线程都是在单个内核执行,其他的内核并没有工作!
???这就有点颠覆我的认知了,后来不断的google、查阅资料我才发现,这个与操作系统CPU的算法有关系!【参考文章】
线程的调度是根据cpu的算法,如果线程的运算量不大,cpu算法调度线程不一定会平均分配给每个内核的。那意思是如果运算量大的话,就会使用到其他的内核咯?
继续改进测试代码:
public class ThreadTest{
// 数据量
private static final int num = 2000 * 1000;
// 设置栅栏是为了防止子线程还没结束就执行main线程输出耗时时间
private static final CountDownLatch countDownLatch = new CountDownLatch(4);
private static ExecutorService service = Executors.newFixedThreadPool(4);
private static final String filePath1 = "/Users/hao/IdeaProjects/Sample/src/test1.txt";
private static final String filePath2 = "/Users/hao/IdeaProjects/Sample/src/test2.txt";
private static final String filePath3 = "/Users/hao/IdeaProjects/Sample/src/test3.txt";
private static final String filePath4 = "/Users/hao/IdeaProjects/Sample/src/test4.txt";
private static File file1 = new File(filePath1);
private static File file2 = new File(filePath2);
private static File file3 = new File(filePath3);
private static File file4 = new File(filePath4);
public static void main(String[] args) throws InterruptedException, IOException {
// 开始时间
long startTime = System.currentTimeMillis();
new Thread(new WriteFileThread(file1),"线程1").start();
new Thread(new WriteFileThread(file2),"线程2").start();
new Thread(new WriteFileThread(file3),"线程3").start();
new Thread(new WriteFileThread(file4),"线程4").start();
try {
countDownLatch.await();
} finally {
service.shutdown();
}
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println();
System.out.println("总耗时间为:" + (endTime - startTime) / 1000.0 + "s");
}
static class WriteFileThread implements Runnable {
private File file;
public WriteFileThread(File file) {
this.file = file;
}
@Override
public void run() {
writeFile(file);
}
}
static void writeFile(File file){
// 判断是否有该文件
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
long startTime = System.currentTimeMillis();
//创建输出缓冲流对象
BufferedWriter bufferedWriter = null;
try {
bufferedWriter = new BufferedWriter(new FileWriter(file));
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 0; i < num; i++) {
try {
bufferedWriter.write(i);
bufferedWriter.newLine();
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "执行完成,耗时 : " + (endTime - startTime) / 1000 + "s");
countDownLatch.countDown();
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出结果:
线程4执行完成,耗时 : 22s
线程3执行完成,耗时 : 22s
线程1执行完成,耗时 : 22s
线程2执行完成,耗时 : 24s
总耗时间为:24.709s
再查看Visual VM 监控工具,可以发现,4个线程都并发的执行了!
Visual VM 监控截图总结:
通过这一个“坑”,使我认识到自己过去一些不良的学习习惯。虽然我了解不少的并发类、并发原理,而且也写了不少的demo实践,但是没想到底层执行却有可能与自己所识的相差甚远。我学习多线程的过程中并没有结合实际,没有试过看监控工具,没有试过调试JVM参数,没有试过压测,怎能证明这些并发工具类真的能优化系统性能呢?与此同时,也明白到自己哪些的不足,首先不熟悉操作系统的知识,这是一块硬伤,过去也常常因为不熟悉操作系统而进入牛角尖、理解不透知识,往后应好好的恶补这块内容,提高“内功”;另外一方面,自己在很多方面总是理所当然,书上、网上博客说什么就认为是什么,没有实践、没有结合自己的思考,这也是我现阶段比较缺乏的一种思维方式,往后也要改正、注意习惯!