简单线程无法完全并行执行

时间:2018-12-30 22:18:21

标签: java android multithreading runnable

我正在启动十个线程来更新十个进度条。
完成栏应该需要五秒钟。
仅启动一个线程时,它将在五秒钟内完成。
当启动所有十个线程时,它们异步更新,并在五秒钟内完成。
是否有可能以更有效的方式完成此任务,以便在十秒钟之内完成所有十个进度条?

这是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> 

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

enter image description here

这是一起开始的时候

enter image description here

编辑:如@ 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);
            }

        }
} 

这是现在的样子。

enter image description here

2 个答案:

答案 0 :(得分:0)

由于1个线程需要5秒钟,而且我怀疑您的设备有10个内核(由于这似乎是平板电脑,所以大概是4个内核中的2个),因此您不会在5秒内完成全部10个内核。如果您执行的线程/条数与内核数一样多,则应在大约5秒钟内完成。如果您使用的线程多于内核,则线程肯定必须共享内核才能完成工作,这将花费更长的时间(但比同步处理的时间要少得多)。即使将线程数与内核进行匹配,也无法保证您可以不间断地访问应用程序中的所有内核-仍然需要运行整个操作系统和后台任务。

由于1个线程需要5秒钟,所以同步10个线程需要50秒钟。 10线程中的6到7秒相当不错。

答案 1 :(得分:0)

正如@DavisHerring在评论中提到的,这(几乎全部)是由于MyRunnable.run()中遗漏了Millis。您的代码假定循环将“看到” System.currentTimeMillis()的完全顺序的返回值。

您可能会认为,由于循环是全速运行的,因此毫无疑问,它可以每毫秒多次调用System.currentTimeMillis(),因此很可能不会丢失任何循环。但是,然后,您拥有的线程多于核心,因此很显然,某些线程必须先抢占一部分时间。

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

因此,如果给定线程在短时间内停止执行,则很容易错过50的倍数。例如,其中一个线程可以从System.currentTimeMillis()中“看到”以下返回值:

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

建议的修补程序

而不是试图抓住每个毫,而是在开始时花一些时间。然后,使progress等于(经过的时间)/ 50:

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

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