简单线程不能完全并行执行

Simple threads don't execute exactly parallely

我正在启动十个线程来更新十个进度条。
完成这些条应该需要五秒钟。
当只启动一个线程时,它会在五秒内完成。
当启动所有 10 个线程时,它们会异步更新并在超过 5 秒的时间内完成。
是否有可能以更有效的方式完成此任务,以便所有十个进度条在五秒钟内完成?

这里是 MainActivity.java

package com.google.example;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    ArrayList<ProgressBar> bars = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for(int i = 0; i < 10; i++){
            int id = getResources().getIdentifier("b" + (i+1), "id", getPackageName());
            bars.add((ProgressBar) findViewById(id));
        }

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                int num = 10;

                for(int i = 0; i < num; i++){
                    MyRunnable runnable = new MyRunnable(bars.get(i));
                    Thread thread = new Thread(runnable);
                    thread.start();
                }

            }
        });

    }

    class MyRunnable implements Runnable{

        ProgressBar bar;

        MyRunnable(ProgressBar b){
            bar = b;
        }

        @Override
        public void run() {
            int progress = 0;
            long previous = 0;

            while(progress < 100) {
                long time = System.currentTimeMillis();
                if (time != previous && time % 50 == 0) {
                    progress++;
                    bar.setProgress(progress);
                    previous = time;
                }
            }

        }
    }
}

这里是activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"/>

    <ProgressBar
        android:id="@+id/b1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b6"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b7"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b8"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b9"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

    <ProgressBar
        android:id="@+id/b10"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"/>

</LinearLayout> 

这是仅启动一个线程时的输出

这是一起开始的时候

编辑:按照@greeble31 的建议,我通过将 MyRunnable 更改为

解决了这个问题
class MyRunnable implements Runnable{

        ProgressBar bar;

        MyRunnable(ProgressBar b){
            bar = b;
        }

        @Override
        public void run() {
            int progress = 0;
            long hack = System.currentTimeMillis();

            while(progress < 100) {
                progress = (int) ((System.currentTimeMillis() - hack) / 50);
                bar.setProgress(progress);
            }

        }
} 

这是现在的样子。

由于 1 个线程需要 5 秒,而且我怀疑您的设备是否有 10 个内核(可能是 4 个中的 2 个,因为这看起来是平板电脑),您无法在 5 秒内完成所有 10 个。如果你做的线程/条数与你的内核数一样多,它应该会在大约 5 秒内完成。如果您使用的线程多于内核,那么线程肯定必须共享内核才能完成工作,并且会花费更长的时间(但仍然比同步少得多)。即使将您的线程数与内核相匹配,也不能保证您可以不间断地访问您的应用程序的所有内核——仍然有一个完整的 OS 和正在进行的后台任务需要 运行。

由于 1 个线程需要 5 秒,因此 10 个同步线程需要 50 秒。 10 个线程 6-7 秒已经很不错了。

正如@DavisHerring 在评论中提到的,这(几乎完全)是由于 MyRunnable.run() 中错过了毫秒。您的代码假定循环将从 System.currentTimeMillis().

"see" 完美顺序 return 值

您可能已经推断,由于循环全速运行,它无疑能够每毫秒调用 System.currentTimeMillis() 多次,因此不太可能错过任何一次。但是,您拥有的线程多于核心,因此显然某些线程必须花费部分时间进行抢占。

此外,在任何抢占式多任务操作系统上,无法保证任何特定线程将在任何特定时间执行。

因此,如果给定线程暂时停止执行,它很容易错过 50 的倍数。例如,其中一个线程可能 "see" 以下 return 来自 System.currentTimeMillis():

1546215544594
1546215544594
1546215544594
1546215544594   <-- thread is unscheduled here, causing a 23ms gap
1546215544617
1546215544617
1546215544617
1546215544617

建议修复

与其试图抓住每一毫,不如在开始时花点时间。然后让 progress 等于 (elapsed time) / 50:

progress = (int) ((System.currentTimeMillis() - hack) / 50);
bar.setProgress(progress);

顺便说一句:System.currentTimeMillis() 不能保证 return 单调递增的结果(参见 System.uptimeTimeMillis() 替代方案),但对于您的特定问题,这可能没有那么大一个问题。