为什么在Java中单线程比多线程更快?

18

根据我的理解,我编写了以下简单的单线程和多线程程序以检查执行速度。但是我的单线程程序比多线程程序运行得更快,请看下面的程序并指出是否有任何错误。

单线程:

import java.util.Calendar;

public class NormalJava {
    public static void main(String[] args) {
        System.out.println("Single Thread");
        int a = 1000;
        int b = 200;
        NormalJava nj = new NormalJava();
        nj.Add(a, b);
        nj.Sub(a, b);
        nj.Mul(a, b);
        nj.Div(a, b);
        Calendar lCDateTime = Calendar.getInstance();
        System.out.println("Calender - Time in milliseconds :"
                + lCDateTime.getTimeInMillis());

    }

    private void Add(int a, int b) {
        System.out.println("Add :::" + (a + b));
    }

    private void Sub(int a, int b) {
        System.out.println("Sub :::" + (a - b));
    }

    private void Mul(int a, int b) {
        System.out.println("Mul :::" + (a * b));
    }

    private void Div(int a, int b) {
        System.out.println("Mul :::" + (a / b));
    }
}

输出:
单线程
加 :::1200
减 :::800
乘 :::200000
乘 :::5
日历 - 时间(毫秒):138 415 866 7863


多线程程序:

package runnableandcallable;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class MainThread {

    private static ExecutorService service = Executors.newFixedThreadPool(10); // connection
                                                                               // pool
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Multithreading");
        MainThread mt = new MainThread();
        mt.testThread(1000, 200);
        Calendar lCDateTime = Calendar.getInstance();
        System.out.println("Calender - Time in milliseconds :"
                + lCDateTime.getTimeInMillis());
    }

    public void testThread(final int a, final int b) {
        // create a callable for each method
        Callable<Void> callableAdd = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                Add(a, b);
                return null;
            }
        };

        Callable<Void> callableSub = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                Sub(a, b);
                return null;
            }
        };

        Callable<Void> callableMul = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                Mul(a, b);
                return null;
            }
        };

        Callable<Void> callableDiv = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                Div(a, b);
                return null;
            }
        };

        // add to a list
        List<Callable<Void>> taskList = new ArrayList<Callable<Void>>();
        taskList.add(callableAdd);
        taskList.add(callableSub);
        taskList.add(callableMul);
        taskList.add(callableDiv);

        // create a pool executor with 3 threads
        ExecutorService executor = Executors.newFixedThreadPool(3);

        try {
            // start the threads
            List<Future<Void>> futureList = executor.invokeAll(taskList);

            for (Future<Void> voidFuture : futureList) {
                try {
                    // check the status of each future. get will block until the
                    // task
                    // completes or the time expires
                    voidFuture.get(100, TimeUnit.MILLISECONDS);
                } catch (ExecutionException e) {
                    System.err
                            .println("Error executing task " + e.getMessage());
                } catch (TimeoutException e) {
                    System.err.println("Timed out executing task"
                            + e.getMessage());
                }

            }

        } catch (InterruptedException ie) {
            // do something if you care about interruption;
        }

    }

    private void Add(int a, int b) {
        System.out.println("Add :::" + (a + b));
    }

    private void Sub(int a, int b) {
        System.out.println("Sub :::" + (a - b));
    }

    private void Mul(int a, int b) {
        System.out.println("Multiply :::" + (a * b));
    }

    private void Div(int a, int b) {
        System.out.println("Division :::" + (a / b));
    }

}

多线程输出:
多线程
Sub :::800
Division :::5
Add :::1200
Multiply :::200000
日历- 毫秒时间 :138 415 868 0821

这里单线程在138 415 866 7863毫秒时执行,而多线程在138 415 868 0821毫秒时执行。那么多线程的真正目的是什么?


那么多线程的真正目的是什么?有两个目的:1.避免短任务被长任务阻塞(例如,在Swing应用程序中至少有两个线程,一个确定要在屏幕上绘制什么,另一个响应按钮点击;如果响应按钮点击的线程也绘制屏幕,则按钮将无法响应或响应缓慢)2.当存在多个“处理单元”(多核/多处理器系统)时,JVM可能会将不同的线程分派到不同的单元,也许加快处理速度。 - John Donn
更多细节:http://softwareengineering.stackexchange.com/questions/97615/what-can-multiple-threads-do-that-a-single-thread-cannot - TooCool
你先运行单线程,然后再运行多线程 - 这个信息是从你输出的时间戳信息中得出的,而不是运行时间。一些答案已经指出了这一点。 - Alex Martian
5个回答

39

你正在进行的处理是微不足道的,因此创建线程的开销更大。

如果有昂贵的操作可以并行完成,则多线程是有意义的。


1
如果您有一些昂贵的操作可以并行处理,那么使用多线程是有意义的。 - Mike Herasimov
1
@Scary Wombat:你能定义一下“昂贵的操作”吗?你是指使用更大的数字进行乘法、加法等运算吗? - cpx
昂贵的操作通常涉及诸如密集的数据库搜索/更新、I/O或其他需要大量CPU的操作,例如用于分析的数字计算。简单的数学通常不会成为竞争者。 - Scary Wombat

8
第一点:创建线程的开销比它们执行的有用工作多。如果你在线程中运行更多的繁重工作,它会比单个线程更快。不必要的代码应该在一个线程中运行。 第二点:针对微基准测试,应该使用JMH

6

1384158667863毫秒大约是44年。所以,您是在告诉我们您等待了44年才得到此操作的结果吗?还是您测量执行速度的方式存在问题?

要测量两个时间之间的差异,您至少需要两个时间,而您只在程序结束时获取当前日期,这甚至不接近准确。

简单的时间测量类:

public class StopWatch {
  private long startTime = -1;

  public void start() {
    this.startTime = System.nanoTime();
  }

  public long timeNanos() {
    return System.nanoTime() - this.startTime;
  }

  public double timeMillis() {
    return this.timeNanos() / 1000000.0;
  }
}

使用这个秒表来测量执行时间(就像使用秒表一样),然后重复3次,你会发现每次得到的结果都完全不同。这是因为精确测量执行时间并不容易。操作系统不断地中断您的程序执行,执行看似简单的命令可能需要运行整个后台命令链。
你能做的只是通过运行该任务一百万次并取平均值来近似所需时间。

4

首先,你在毫秒中计算的时间戳只是时间戳本身。要测量经过的时间,你需要计算调用前后的时间差。我猜你先运行了单线程应用程序。如果你先尝试运行多线程应用程序,你会发现它具有更低的“毫秒时间”值。

其次,创建和管理线程的开销远高于你执行的非常简单的算术操作的运行时间。如果你尝试迭代这些操作几百万次,可以通过并行执行操作来提高性能。


2
如果考虑一个单处理器机器,所有线程都在一个处理器上运行。假设您的程序(jvm)每秒在处理器上有0.2秒的执行时间。 如果您在单个线程上执行,则这0.2秒将仅用于此主线程。 如果您例如在4个线程上执行,则0.2秒将不会分配为0.05 + 0.05 + 0.05 + 0.05。您需要添加额外的时间来同步、恢复和释放线程。假设每次上下文切换需要0.001秒,每秒提供一个线程的执行时间则会损失0.004秒的执行时间。 在现实生活中,线程上下文切换在每秒钟内会发生很多次,并且是无法预测的。 现在情况正在改变,因为现在有多核机器,线程可以同时在不同的核心上执行。
请查看此链接以获取更多信息:Java是否支持多核处理器/并行处理?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接