java - 为什么在多线程环境中用虚拟记录填充数组列表要花费双倍的时间?

标签 java multithreading threadpool executorservice threadpoolexecutor

我试图使用大小为 4 的线程池(在八核处理器上)在数组列表中添加 1000 万条记录。但与单线程代码相比,它花费的时间是单线程代码的两倍。

下面是代码片段。我可能做错了什么。谁能解释一下代码中的问题是什么?

package com.shree.test;

public class Student {
    private int id;
    private String name;
    private int age;
    private int std;

    public Student(int id, String name, int age, int std) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.std = std;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getStd() {
        return std;
    }
    public void setStd(int std) {
        this.std = std;
    }
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + ", std=" + std + "]";
    }
}

多线程代码(使用线程池):

    package com.shree.test;

    import java.time.LocalDateTime;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    import org.apache.commons.lang3.RandomStringUtils;
    import org.apache.commons.lang3.RandomUtils;

    class Task implements Callable<Student>{

        private static final String CHAR_SET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private int id;
        private List<Student> studentList;

        public Task(int id,List<Student> studentList) {
            this.id = id;
            this.studentList = studentList;
        }

        @Override
        public Student call() throws Exception {
            Student student =  new Student(id, RandomStringUtils.random(RandomUtils.nextInt(5, 10), CHAR_SET), RandomUtils.nextInt(10, 15), RandomUtils.nextInt(4, 9));
            studentList.add(student);
            return student;
        }
    }

    public class MultiThreadStudentListGenerator {

        private List<Student> students = Collections.synchronizedList(new ArrayList<>());

        private ExecutorService threadPool = Executors.newFixedThreadPool(4);

        public void generateStudentList() {
            for(int i=0;i<10000000;i++) {
                threadPool.submit(new Task(i, students));
            }
            threadPool.shutdown();  
        }

        public void process() {
            generateStudentList();
        }

        public int getSize() {
            return students.size();
        }

        public void addShutDownhook(LocalDateTime dateTime1 ) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    LocalDateTime dateTime2 = LocalDateTime.now();

                    long diffInMilli = java.time.Duration.between(dateTime1, dateTime2)
                            .toMillis();

                    System.out.println("Time taken in Miliseconds: " + diffInMilli);
                    System.out.println("List Size: " + getSize());
                }
            });
        }

        public static void main(String[] args) {

            MultiThreadStudentListGenerator multiThreadStudentListGenerator = new MultiThreadStudentListGenerator();

            LocalDateTime dateTime1 = LocalDateTime.now();
            multiThreadStudentListGenerator.addShutDownhook(dateTime1);

            multiThreadStudentListGenerator.process();
        }
    }

单线程代码:

package com.shree.test;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;

public class SingleThreadStudentListGenerator {

    private static final String CHAR_SET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private List<Student> students = new ArrayList<>();

    public void generateStudentList() {
        for (int i = 0; i < 10000000; i++) {
            Student student = new Student(i, RandomStringUtils.random(RandomUtils.nextInt(5, 10), CHAR_SET),
                    RandomUtils.nextInt(10, 15), RandomUtils.nextInt(4, 9));
            students.add(student);
        }
    }

    public void process() {
        generateStudentList();
    }

    public int getSize() {
        return students.size();
    }

    public void addShutDownhook(LocalDateTime dateTime1) {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                LocalDateTime dateTime2 = LocalDateTime.now();

                long diffInMilli = java.time.Duration.between(dateTime1, dateTime2).toMillis();

                System.out.println("Time taken in Miliseconds: " + diffInMilli);
                System.out.println("Size: " + getSize());
            }
        });
    }

    public static void main(String[] args) {

        SingleThreadStudentListGenerator mainClass = new SingleThreadStudentListGenerator();

        LocalDateTime dateTime1 = LocalDateTime.now();
        mainClass.addShutDownhook(dateTime1);

        mainClass.process();

    }
}

最佳答案

两个主要问题:

  • 如何衡量。您使用关闭 Hook 的想法确实很奇怪。您应该使用像 JMH 这样的框架来进行此类测试,请参阅 here获取如何获得有意义数字的指导
  • 那么,这里:Collections.synchronizedList(new ArrayList<>()) 。这将创建一个同步添加/删除请求的列表。这意味着:锁定。你猜怎么着:锁定的成本很高。

换句话说:A)您获取号码的方式是可疑的,B)锁定比不锁定更昂贵。在您的情况下,您的 4 个线程将不断相互冲突,并且必须等待另一个线程完成对列表的修改。

试想一下:当您有一把铲子,并且需要挖一个洞时……雇用 4 个人来做这项工作真的会对您有利吗?或者更愿意是:三个人看着第四个使用铲子?!

如:请注意,使用多个线程只能在特定情况下节省总体执行时间,例如当您的工作负载需要经常等待 I/O 时。 CPU 密集型工作负载(这就是您的代码正在做的事情)不会从“更多线程”中获益。与此相反的。更多线程,这可能意味着上下文切换、锁定、CPU 缓存使用效率较低等。

因此:如果您确实希望看到使用多线程带来的改进,请从肯定会从多线程中受益的工作负载开始。例如:当您连接到 X 网站并下载内容时...那么您将从使用多线程执行此操作中受益匪浅。

关于java - 为什么在多线程环境中用虚拟记录填充数组列表要花费双倍的时间?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58509945/

相关文章:

java - list 文件不包含我项目中的正确 Activity

java - 如何在 Camel quickfixj 组件中启用多线程

c++ - 为什么我的程序在 1 个线程上比在 8.C++ 上运行得更快

java - Java 实现中的 BouncyCaSTLe PGP 文本模式不会转换为 CR/LF

java - 如何收集递归方法的结果

java - 如何从应用程序的 "small"版本重定向到 "big"版本

c++ - 寻找设计建议 - 统计记者

java - 使用脚本运行 java jar 文件

c# - 这是使用线程池的正确案例吗?

Java-使用 invokeAll 按顺序获取 future 结果,但仅适用于某些线程