0

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)
        {
        }
    }
}
Trevor
  • 218
  • 3
  • 15
  • "console" support is really basic in Java. You could do [something like this example](https://stackoverflow.com/questions/15843202/how-to-show-percentage-progress-of-a-downloading-file-in-java-consolewithout-ui/15846788#15846788) which basic uses a backspace character to remove characters previously printed in the console. If you want something more complex, then you're going to have to investigate using one of the Curses implementations – MadProgrammer Mar 07 '18 at 06:50

1 Answers1

1

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.

Mars
  • 4,677
  • 8
  • 43
  • 65
  • Thanks for your help. Yes it makes sense to put the execution in one place. What I was hoping to get out of this multi-threaded app was to enable another class (any object) to provide the progress updates (values only) to another class (ie ProgressValue) where the thread can have access to it, read the current progress data and then display the progress bar. Would it work that way? Possible? – Trevor Mar 07 '18 at 10:22
  • @Trevor. Yes that is possible. To do so you will need to rely on some condition to display the progress bar rather than having it display over a fixed interval. In that approach you would probably make use of `wait()`, `notify()` or a similar type of synchronization mechanism. – Mars Mar 10 '18 at 20:15