简单的Java多线程测试不工作



我编写了一些java代码,使用while循环在1秒内尽可能多地增加一个值。

然后我尝试使用4个线程在1秒内尽可能多地增加四个值。我原以为四个线程的值是简单循环的四倍。

代码如下:

package threadTest;
public class ThreadTestMain {
public static void main(String[] args) {
    long end_time = 0;
    long value = 0;
    long runtime = 0;
    long start_time = System.currentTimeMillis();
    while (runtime<=1000) {
        value++;
        runtime = System.currentTimeMillis() - start_time;
    }
    System.out.println(value);
    end_time = System.currentTimeMillis();
    System.out.println("DEBUG-> Time should be 1sec : " + (end_time - start_time));
    SeperateExecution myObject = new SeperateExecution(0);
    SeperateExecution myObject2 = new SeperateExecution(0);
    SeperateExecution myObject3 = new SeperateExecution(0);
    SeperateExecution myObject4 = new SeperateExecution(0);
    Thread worker1 = new Thread(myObject,"worker1");
    Thread worker2 = new Thread(myObject2,"worker2");
    Thread worker3 = new Thread(myObject3,"worker3");
    Thread worker4 = new Thread(myObject4,"worker4");
    worker1.start();worker2.start();worker3.start();worker4.start();
}
}

package threadTest;
//import java.util.concurrent.atomic.AtomicLong;
public class SeperateExecution implements Runnable {
private long value = 0;
//private AtomicLong value = null;
private long start_time = 0;
public SeperateExecution (long p_val)
{
    this.value = p_val;
    //this.value = new AtomicLong(p_val);
    //this.start_time = p_st;
}
@Override
public void run(){
    long runtime = 0;
    this.start_time = System.currentTimeMillis();
    while (runtime<=1000) {
        //value.incrementAndGet();
        value++;
        runtime = System.currentTimeMillis() - start_time;
    }
    System.out.println(Thread.currentThread().getName() + " DONE " + value + 
            " Thread Exection Time " + (System.currentTimeMillis()- start_time));
}
}

代码输出如下:

52266551
DEBUG-> Time should be 1sec : 1001
worker1 DONE 13364059 Thread Exection Time 1001
worker2 DONE 13744972 Thread Exection Time 1001
worker4 DONE 11652084 Thread Exection Time 1001
worker3 DONE 13605645 Thread Exection Time 1001

有人能帮助我理解为什么多线程值总和小于正常while循环的值吗?

用一个比喻来帮助理解我的问题:如果一个工人一天能挖一个洞,那么四个工人一天就能挖四个洞。

谢谢

结果的原因似乎很明显:System.currentTimeMillis在某个地方获得了一个锁,这使得代码基本上是顺序的(内核调用是迄今为止在循环中完成的最昂贵的事情)。

在这种情况下,您期望四个线程的数量与顺序版本大致相同,这几乎就是发生的情况。

有趣的问题是为什么System.currentTimeMillis需要锁?这是在src/os/<os>/vm/os_<os>.cpp中实现的本地函数,其中<os>是linux, windows或solaris。稍微简化的函数在jdk9 dev树中如下所示:

jlong os::javaTimeMillis() {
    FILETIME wt;
    GetSystemTimeAsFileTime(&wt);
    return windows_to_java_time(wt);
}

所以Java本身不同步,因为GetSystemTimeAsFileTime是线程安全的。所以我们可以假设操作系统内部为这个方法获取了一个锁——相当令人惊讶,我真的不明白为什么这是必要的。所使用的结构体只有8个字节,因此可以自动更新。

无论如何,如果在Linux上运行相同的代码不会出现这种行为,我不会感到惊讶。

我认为在所有线程中获得相同的数字是不可能的。

  1. 您可能有4个处理器,但没有必要将所有4个处理器都专用于java程序。它可能会将周期空间交换给其他进程(如操作系统进程,其他正在运行的进程…)

  2. 我用简单的循环清理了你的代码,没有System.currentMill

结果如下

Thread3 Value :413907923
Thread1 Value :431271274
Thread2 Value :426025618
Thread4 Value :431386076

我尝试了8个线程(我有4个核心),这是结果

Thread7 Value :441240435
Thread4 Value :283265150
Thread2 Value :427759029
Thread3 Value :441240435
Thread8 Value :283265150
Thread6 Value :427759029
Thread5 Value :450884067
Thread1 Value :450884067

(连SOP都没按顺序做)

public class SimpleTask implements Runnable {
    private long value = 0;
    private volatile static int oneSecondCompleted = 0;
    public SimpleTask(long lValue) {
        this.value = lValue;
    }
    @Override
    public void run() {
        while(oneSecondCompleted <= 1){
            if (oneSecondCompleted == 1){
                while (oneSecondCompleted == 1){
                    this.value++;
                }
            }
        }
        System.out.println(Thread.currentThread().getName() +" Value :"+value);
    }
    public static void main(String args[]){
        SimpleTask task1 = new SimpleTask(0);
        SimpleTask task2 = new SimpleTask(0);
        SimpleTask task3 = new SimpleTask(0);
        SimpleTask task4 = new SimpleTask(0);
        Thread thread1 = new Thread(task1,"Thread1");
        Thread thread2 = new Thread(task2,"Thread2");
        Thread thread3 = new Thread(task3,"Thread3");
        Thread thread4 = new Thread(task4,"Thread4");
        thread1.start();thread2.start();thread3.start();thread4.start();
        oneSecondCompleted = 1;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        oneSecondCompleted = 2;
    }
}

可能内核在系统时间上只响应一个调用,而将其他调用排在队列中。例如,如果它在某处有一个同步调用。由于它的性能是限制因素,所以要测量可以调用System.currenttime()的次数,这两种情况下的次数大致相同。剩下的区别在于上下文切换和维护线程的一般开销。

试着在while循环中做一个for look,这样它每添加1000次左右才检查一次时间条件。这应该足以使系统。当前时间不再是限制因素。

看看何时启动计时器:您给每个SeparateExecution对象一秒钟的时间来计数,但从何时开始呢?

你的第一个对象的一秒钟包括创建所有四个SeparateExecution对象所需的时间加上创建所有四个线程所需的时间,再加上它自己的线程启动所需的时间。最后一个对象的一秒钟包括创建一个SeparateExecution实例所花费的时间,加上创建四个线程对象所花费的时间,加上三个start()调用所花费的时间,再加上它自己的线程启动时间。这是一个很大的缺陷。


编辑:这是我的版本。它使用本地计数器进行一次测量,使用实例变量计数器进行另一次测量,然后启动并发执行实例变量测试的线程(我的PC上有8个线程)。当我运行它时,前两次运行的计数大致相同,大约比并发运行产生的计数高1.5倍。
public class Threadly {
    public static void main(String[] args) throws InterruptedException {
        doLocalVar();
        doInMainThread();
        doMultiThreaded();
    }
    private static void doLocalVar() {
        long count = 0L;
        long startTime = System.currentTimeMillis();
        long endTime = startTime;
        while (endTime - startTime < 1000L) {
            count += 1L;
            endTime = System.currentTimeMillis();
        }
        System.out.println("local: elapsed=" + (endTime - startTime) + ", count=" + count);
    }
    private static void doInMainThread() {
        Spinner spinner = new Spinner("main");
        spinner.run();
        spinner.show();
    }
    private static void doMultiThreaded() throws InterruptedException {
        int numThreads = Runtime.getRuntime().availableProcessors();
        Thread[] threads = new Thread[numThreads];
        Spinner[] spinners = new Spinner[numThreads];
        for (int i=0 ; i<numThreads ; i++) {
            spinners[i] = new Spinner("t-" + Integer.toString(i));
            threads[i] = new Thread(spinners[i]);
        }
        for (Thread thread : threads)
            thread.start();
        for (Thread thread : threads)
            thread.join();
        for (Spinner spinner : spinners)
            spinner.show();
    }
    private static class Spinner implements Runnable {
        private long startTime;
        private long endTime;
        private long count;
        private final String name;
        Spinner(String name) {
            this.name = name;
        }
        // @Override
        public void run() {
            count = 0L;
            startTime = System.currentTimeMillis();
            endTime = startTime;
            while (endTime - startTime < 1000L) {
                count += 1L;
                endTime = System.currentTimeMillis();
            }
        }
        void show() {
            System.out.println(name + ": elapsed=" + (endTime - startTime) + ", count=" + count);
        }
    }
}

最新更新