简体   繁体   中英

How to create a threaded console progress bar in Java?

I am trying to create a working progress bar program in Java so it can support both console and GUI applications.

The idea is use the thread to provide the current progress information, but it seems the thread code is not working well. Concurrency is so new to me.

I want it to advance the bar every time it is stepped up by one rather than completing the loop prematurely before the progress bar catches up. I guess the problem is timing?

[=====>                                            ] 10%  1
2
3
4
5
6
7
8
9
10

[==================================================] 100%  

Can someone tell me what I have gone wrong?

Main code

package console;
import java.util.ArrayList;
import console.ProgressThread;

public class ConsoleProgressBar
{
    private static final long REFRESH_DELAY = 50;
    private ProgressValue progress;
    private ProgressThread target;
    private Thread thread;

    protected static class ProgressValue
    {
        protected long total = 0;
        protected long current = 0;

        protected ProgressValue(long n)
        {
            total = n;
        }

        protected synchronized void setMaxTotal(long n)
        {
            total = n;
        }

        protected synchronized void stepBy(long n)
        {
            current = current + n;

            if (current > total) total = current;
        }

        protected synchronized void stepTo(long n)
        {
            current = n;

            if (current > total) total = current;
        }

        protected synchronized long getCurrent()
        {
            return current;
        }

        protected synchronized long getTotal()
        {
            return total;
        }
    }

    public ConsoleProgressBar(long totalItem)
    {
        this(totalItem, REFRESH_DELAY);
    }

    public ConsoleProgressBar(long totalItem, long refreshDelay)
    {
        progress = new ProgressValue(totalItem);
        target = new ProgressThread(progress, refreshDelay);
    }

    public void start()
    {
        thread = new Thread(target);
        thread.start();
    }

    public void stepBy(long n)
    {
        progress.stepBy(n);
    }

    public void stepTo(long n)
    {
        progress.stepTo(n);
    }

    public void step()
    {
        progress.stepBy(1);
    }

    public void setMaxTotal(long n)
    {
        progress.setMaxTotal(n);
    }

    public void stop()
    {
        target.terminate();

        try
        {
            thread.join();
        }

        catch (InterruptedException ex)
        {
        }
    }

    public long getCurrent()
    {
        return progress.getCurrent();
    }

    public long getTotal()
    {
        return progress.getTotal();
    }

    public static void main(String[] args)
    {
        ArrayList<Integer> test = new ArrayList<>();
        ConsoleProgressBar bar = new ConsoleProgressBar(10, 50);

        bar.start();

        for (int i = 0; i < 10; i++)
        {
            int sum = i + 5;

            test.add(sum);

            bar.step();

            System.out.format("%s%n", bar.getCurrent());
        }

        bar.stop();
    }
}

Thread code

package console;

import console.ConsoleProgressBar.ProgressValue;

public class ProgressThread implements Runnable
{
    private static final int WIDTH = 50;
    private volatile boolean terminated;
    private ProgressValue progressRef;
    private long timeMS;

    public ProgressThread(ProgressValue ref, long refreshDelay)
    {
        progressRef = ref;
        timeMS = refreshDelay;
        terminated = false;
    }

    private void refreshProgressBar()
    {
        StringBuilder sb = new StringBuilder("\r[");
        int percent = (int) Math.floor(100.0 * progressRef.current / progressRef.total);

        for (int i = 0; i < WIDTH; i++)
        {
            if (i < (percent / 2)) sb.append("=");
            else if (i == (percent / 2)) sb.append(">");
            else sb.append(" ");
        }

        sb.append("] %s  ");

        if (percent >= 100) sb.append("%n");

        System.out.printf(sb.toString(), percent + "%");
    }

    void terminate()
    {
        terminated = true;
    }

    public void run()
    {
        try
        {
            while (terminated == false)
            {
                refreshProgressBar();
                Thread.sleep(timeMS);
            }

            refreshProgressBar();
        }

        catch (InterruptedException exc)
        {
        }
    }
}

Why do you need a multithreaded application when it is just one task you are trying to achieve?

Nonetheless, to achieve what you want I suggest moving your execution entirely into either the thread class or into the main class.

If the main application is going to run something else, then ideally you'd put the execution in the thread class. However here I've put the execution into the main class. It could also just as easily go in the thread class.

As an example, I've edited run() in ProgressThread to just be this,

public void run()
{
    while( terminated )
    {
    }
}

And I edited main in ConsoleProgressBar to this,

public static void main(String[] args)
{
    ArrayList<Integer> test = new ArrayList<>();
    ConsoleProgressBar bar = new ConsoleProgressBar(10, 50);

    bar.start();

    for (int i = 0; i <= 10; i++)
    {
        int sum = i + 5;

        test.add(sum);

        bar.refreshProgressBar();
        System.out.format( "%s", bar.getCurrent() );
        bar.step();
        bar.sleep( 1000 );
    }

    bar.stop();
}

Note that I added the methods sleep( int n ) and refreshProgressBar() to bar so I can call the thread methods, similar to what you did with bar.start() and bar.stop() .

To be clear, in ProgressThread I changed refreshProgressBar to public just for the sake of the example and added the following,

void sleep( int n )
{
    try
    {
        Thread.sleep( n );
    }
    catch( InterruptedException ie )
    {
        ie.printStackTrace();
    }
}

and the following to ConsoleProgressBar ,

private void sleep( int n )
{
    target.sleep( n );
}

private void refreshProgressBar()
{
    target.refreshProgressBar();

}

The output (each line printing at one second intervals) is,

[>                                                 ] 0%  0
[=====>                                            ] 10%  1
[==========>                                       ] 20%  2
[===============>                                  ] 30%  3
[====================>                             ] 40%  4
[=========================>                        ] 50%  5
[==============================>                   ] 60%  6
[===================================>              ] 70%  7
[========================================>         ] 80%  8
[=============================================>    ] 90%  9
[==================================================] 100%  10

Not sure if this is what you are looking for but I suggest putting the execution into one place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM