Search code examples
javamultithreadingswingparallel-processingsemaphore

How can I do a progressive bar with JProgressBar from Swing on Java?


I'm a beginner on parallel computing. I've been working on Java in Apache Netbeans. My recent work was about semaphores, using the Semaphore and Thread Java classes, in that sense, I had to make a program that simulates with progress bars a credit card delivery process that depends on five sub processes (simulated with the threads). Each sub process has a time that is simulated in the program, in this case, using the method sleep from the threads. The semaphore implementation is to progressively increment the general bar depending on the other processes, if one of these is completed then it can be filled in the general bar. Something extra to add is that the sub processes also have their respective progress bars. My problem occurs in the classes BarraGeneral and SubProceso, these represent the general process and one sub process. In their overriden run methods is where I simulate the time and, of course, I do the drawing, but this is not happening. I can say it actually works because on console you can check the prints, the problem as I said is that the bars are not reflecting this progress, it just goes from 0% to 100%.

I tried to solve it by reducing the sleeping simulation time, but that is not the problem. Also I debugged, and the bars are actually changing its value in each iteration.

Code:

package espe.edu.ec.laboratorio1p2;

import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JProgressBar;

/**
 *
 * @author DARÍO BENAVIDES
 */
public class BarraGeneral extends Thread{
    
    private int id;
    private Semaphore aportes;
    private ArrayList<Integer> porcentajes;
    private int nHilos;
    private JProgressBar bar;
    
    BarraGeneral(int id, Semaphore aportes, ArrayList<Integer> porcentajes, int nHilos, JProgressBar bar){
        this.id = id;
        this.aportes = aportes;
        this.porcentajes = porcentajes;
        this.nHilos = nHilos;
        this.bar = bar;
    }
    
    @Override
    public void run(){
        int a;
        for(int i = 0; i < nHilos; i++){
            try {
                aportes.acquire();
                a = bar.getValue();
                bar.setValue(a + porcentajes.get(i));
                System.out.println(porcentajes.toString());
            } catch (InterruptedException ex) {
                Logger.getLogger(BarraGeneral.class.getName()).log(Level.SEVERE, null, ex);
            }
            
        }
    }
    
}

package espe.edu.ec.laboratorio1p2;

import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JProgressBar;
import javax.swing.event.ChangeListener;

/**
 *
 * @author DARÍO BENAVIDES
 */
public class SubProceso extends Thread{
    private Semaphore aportes;
    private int id;
    private int tiempo;
    private int porcentaje;
    private ArrayList<Integer> porcentajes;
    private JProgressBar bar;
    
    SubProceso(Semaphore aportes, int id, int tiempo, int porcentaje, ArrayList<Integer> porcentajes, JProgressBar bar){
        this.aportes = aportes;
        this.id = id;
        this.tiempo = tiempo;
        this.porcentaje = porcentaje;
        this.porcentajes = porcentajes;
        this.bar = bar;
    }
    
    @Override
    public void run(){
        for(int i = 1; i <= 100; i++){
            try{
                this.sleep(tiempo);
                bar.setValue(i);
            }catch(InterruptedException ex){
                Logger.getLogger(SubProceso.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        System.out.println(porcentaje);
        porcentajes.add(porcentaje);
        aportes.release();
    }
}

package espe.edu.ec.laboratorio1p2;

import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import javax.swing.JProgressBar;

/**
 *
 * @author DARÍO BENAVIDES
 */
public class Laboratorio1P2 {

    public static void correr(JProgressBar barProgresoGeneral, 
            JProgressBar barProgreso1, JProgressBar barProgreso2, 
            JProgressBar barProgreso3, JProgressBar barProgreso4, 
            JProgressBar barProgreso5) {
        
        int nHilos = 5;
        Semaphore aportes = new Semaphore(0);
        ArrayList<Integer> porcentajes = new ArrayList<Integer>();
        ArrayList<SubProceso> subProcesos = new ArrayList<>();
        BarraGeneral barraGeneral = new BarraGeneral(1, aportes, porcentajes, nHilos, barProgresoGeneral);
        
        subProcesos.add(new SubProceso(aportes, 1, 50, 40, porcentajes, barProgreso1));
        subProcesos.add(new SubProceso(aportes,  2, 100, 10, porcentajes, barProgreso2));
        subProcesos.add(new SubProceso(aportes,  3, 100, 10, porcentajes, barProgreso3));
        subProcesos.add(new SubProceso(aportes, 4, 50, 5, porcentajes, barProgreso4));
        subProcesos.add(new SubProceso(aportes,  5, 100, 35, porcentajes, barProgreso5));
        
        barraGeneral.start();
        
        for(int i = 0; i < nHilos; i++){
            subProcesos.get(i).start();
        }
        
        while(barraGeneral.isAlive() || subProcesos.get(0).isAlive() || subProcesos.get(1).isAlive() || subProcesos.get(2).isAlive() ||
                subProcesos.get(3).isAlive() || subProcesos.get(4).isAlive()){
        }
    
    }
}


When clicking on a button from a JFrame I run the program:

    private void iniciarActionPerformed(java.awt.event.ActionEvent evt) {                                        
        Laboratorio1P2.correr(barProgresoGeneral, barProceso1, barProceso2, 
                barProceso3, barProceso4, barProceso5);
        
    }    

Solution

  • It looks like you click the button, then you have a main process that keeps track of your subprocesses. As a subprocess finishes your main process is updated.

    Ditch the busy loop happening on the EDT.

    while(barraGeneral.isAlive() || subProcesos.get(0).isAlive() || subProcesos.get(1).isAlive() || subProcesos.get(2).isAlive() ||
                subProcesos.get(3).isAlive() || subProcesos.get(4).isAlive()){
        }
    

    Anything you call/use from the EDT needs to finish rather quickly*. This busy loop blocks the EDT and prevents any repainting or updates.

    *There is a caveat to this, such as waiting for a dialog to complete where swing/awt starts another EDT.

    I think this is the smallest changes that will "fix" your issue. Personally I would remove your BarraGeneral since you don't need it as another thread. Change your SubProceso to a SwingWorker where the 'run' method is the do in the background. Then Override the done method to update the general progress bar. (Which happens on the EDT after the background task finishes. Where all of your progress bar updates should happen!)