Concurrency Demo

Anleitungen usw. rund um Java, Tutorials halt.

Moderator: wegus

Benutzeravatar
Olek77
Beiträge: 669
Registriert: 21.03.2009, 13:09

Concurrency Demo

Beitragvon Olek77 » 17.01.2011, 14:59

Hallo Allerseits,

Ich habe mir mal die kleine Mühe gemacht
eine Applikation zu schreiben, die Mehrläufigkeit ( multiple threading )
demonstriert und dabei auch die Zwischenergebnisse in einer GUI aktualisiert.

Es handelt sich um einen simplen Zähler, der alle Dateien ( nicht Verzeichnisse )
ab einem bestimmten rootFolder zählt.
Dafür wird für jeden Folder ein neuer Thread gestartet.
Die Threads werden über einen ExecutorService verwaltet.
Um die Aktualsierungen flüssig im GUI anzuzeigen verwende ich den alt bewährten
SwingWorker.

Gerne nehme ich Kritik oder Vorschläge entgegen.
Viel Spaß beim Testen.

Die Main-class :

Code: Alles auswählen

package concurrency;

import concurrency.gui.MainFrame;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

/**
 *
 * @author Olek77
 */
public class Main {

    private static final String nimbusLF = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
    private static final String defaultLF = UIManager.getCrossPlatformLookAndFeelClassName();
    private static final String systemLF = UIManager.getSystemLookAndFeelClassName();

    private static void setLookAndFeel( String type ) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
        UIManager.setLookAndFeel( type );
    }

    /**
     * @param args the command line arguments
     */
    public static void main( String[] args ) {
        try {
            setLookAndFeel( systemLF );
        } catch ( ClassNotFoundException ex ) {
            Logger.getLogger( Main.class.getName() ).log( Level.SEVERE, null, ex );
        } catch ( InstantiationException ex ) {
            Logger.getLogger( Main.class.getName() ).log( Level.SEVERE, null, ex );
        } catch ( IllegalAccessException ex ) {
            Logger.getLogger( Main.class.getName() ).log( Level.SEVERE, null, ex );
        } catch ( UnsupportedLookAndFeelException ex ) {
            Logger.getLogger( Main.class.getName() ).log( Level.SEVERE, null, ex );
        }

        SwingUtilities.invokeLater( new Runnable() {

            public void run() {
                // starte Hauptfenster in EDT
                MainFrame mainFrame = new MainFrame();

            }
        } );
    }
}



Die Klasse MainFrame :

Code: Alles auswählen

package concurrency.gui;

import concurrency.multithreading.FileCounter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.swing.JFileChooser;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.SpringLayout;
import javax.swing.SwingWorker;
import javax.swing.filechooser.FileSystemView;
import org.jdesktop.swingx.JXBusyLabel;
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXLabel;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.JXTextArea;
import org.jdesktop.swingx.JXTextField;

/**
 * Eine kleine Demo fuer Nebenlaeufige Ausfuehrung von Prozessen,
 * deren Zwischeresultate "ohne zu Ruckeln" im UI wiedergegeben werden.
 *
 * Fuer die SwingX-Bibliothek : https://swingx.dev.java.net
 *
 *
 *
 * @author Olek77
 */
public class MainFrame extends JXFrame implements ActionListener {

    private FileScanWorker fsw;
    private JXTextField searchField;
    private JXButton goB, fileChooserB;
    private JXTextArea processArea;
    private JXLabel folderLab, resultLab, statusLab;
    private JXBusyLabel busyLab;
    private JProgressBar globalStatPBar;

    public MainFrame() {
        super( "Thread Demo", true );

        setSize( 1000,
                Toolkit.getDefaultToolkit().getScreenSize().height - 400 );
        setStartPosition( JXFrame.StartPosition.CenterInScreen );

        createMainFrame();
        setVisible( true );

    }

    private void createMainFrame() {
        setLayout( new BorderLayout() );

        add( createConcurrencyPanel(), BorderLayout.CENTER );

    }

    /**
     * Panel welches die Aktivitaeten anzeigt
     *
     * @return
     */
    private JXPanel createConcurrencyPanel() {
        JXPanel rc = null;

        rc = new JXPanel();

        SpringLayout sl = new SpringLayout();

        rc.setLayout( sl );

        searchField = new JXTextField( "Suche hier" );
        searchField.setPreferredSize( new Dimension( 200, 24 ) );

        folderLab = new JXLabel();
        folderLab.setPreferredSize( new Dimension( 700, 24 ) );

        resultLab = new JXLabel();
        resultLab.setPreferredSize( new Dimension( 550, 24 ) );
        resultLab.setFont( new Font( "Courier", Font.BOLD, 20 ) );
        resultLab.setForeground( Color.RED );

        statusLab = new JXLabel();
        statusLab.setPreferredSize( new Dimension( 250, 24 ) );
        statusLab.setFont( new Font( "Courier", Font.BOLD, 20 ) );
        statusLab.setForeground( Color.ORANGE );
       
        busyLab = new JXBusyLabel( new Dimension( 50, 50 ) );

        processArea = new JXTextArea();

        globalStatPBar = new JProgressBar();
        globalStatPBar.setPreferredSize( new Dimension( 200, 24 ) );

        JScrollPane scrPane = new JScrollPane( processArea );
        scrPane.setPreferredSize( new Dimension( 600, 400 ) );

        goB = new JXButton( "Start" );
        goB.addActionListener( this );
        goB.setPreferredSize( new Dimension( 80, 24 ) );

        fileChooserB = new JXButton( "..." );
        fileChooserB.addActionListener( this );
        fileChooserB.setPreferredSize( new Dimension( 80, 24 ) );

        sl.putConstraint( SpringLayout.NORTH, searchField, 10,
                SpringLayout.NORTH, rc );
        sl.putConstraint( SpringLayout.WEST, searchField, 15,
                SpringLayout.WEST, rc );
        sl.putConstraint( SpringLayout.NORTH, fileChooserB, 10,
                SpringLayout.NORTH, rc );
        sl.putConstraint( SpringLayout.WEST, fileChooserB, 15,
                SpringLayout.EAST, searchField );
        sl.putConstraint( SpringLayout.NORTH, goB, 10,
                SpringLayout.SOUTH, searchField );
        sl.putConstraint( SpringLayout.WEST, goB, 15,
                SpringLayout.WEST, rc );
        sl.putConstraint( SpringLayout.NORTH, statusLab, 10,
                SpringLayout.SOUTH, searchField );
        sl.putConstraint( SpringLayout.WEST, statusLab, 15,
                SpringLayout.EAST, goB );
        sl.putConstraint( SpringLayout.NORTH, resultLab, 10,
                SpringLayout.SOUTH, searchField );
        sl.putConstraint( SpringLayout.WEST, resultLab, 15,
                SpringLayout.EAST, statusLab );
        sl.putConstraint( SpringLayout.NORTH, folderLab, 10,
                SpringLayout.SOUTH, goB );
        sl.putConstraint( SpringLayout.WEST, folderLab, 15,
                SpringLayout.WEST, rc );
        sl.putConstraint( SpringLayout.NORTH, globalStatPBar, 10,
                SpringLayout.SOUTH, folderLab );
        sl.putConstraint( SpringLayout.WEST, globalStatPBar, 15,
                SpringLayout.WEST, rc );
        sl.putConstraint( SpringLayout.NORTH, scrPane, 10,
                SpringLayout.SOUTH, globalStatPBar );
        sl.putConstraint( SpringLayout.WEST, scrPane, 15,
                SpringLayout.WEST, rc );
        sl.putConstraint( SpringLayout.NORTH, busyLab, 10,
                SpringLayout.SOUTH, scrPane );
        sl.putConstraint( SpringLayout.WEST, busyLab, 15,
                SpringLayout.WEST, rc );

        rc.add( searchField );
        rc.add( fileChooserB );
        rc.add( goB );
        rc.add( statusLab );
        rc.add( resultLab );
        rc.add( folderLab );
        rc.add( globalStatPBar );
        rc.add( scrPane );
        rc.add( busyLab );
        return rc;
    }

    private String getSelectedFile() {
        String path = null;

        JFileChooser fc = new JFileChooser( System.getProperty( "user.dir") );
        fc.setMultiSelectionEnabled( false );
        fc.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
        int ret = fc.showOpenDialog( null );
        if( ret == JFileChooser.APPROVE_OPTION ){
            path = fc.getSelectedFile().getAbsolutePath();
        }

        return path;
    }

    /**
     * Reiche die Zwischenergebnisse zum Worker durch. Da
     * nur aus der Klasse die den Worker erweitert publish
     * aufgerufen werden kann.
     *
     * @param processInfo
     */
    public synchronized void doPublish( String processInfo ) {

        fsw.doPublish( processInfo );

    }

    public void actionPerformed( ActionEvent ae ) {

        if ( ae.getSource() == goB ) {
            startProcess();
        } else if( ae.getSource() == fileChooserB ) {
            searchField.setText( getSelectedFile() );
        }
    }

    /**
     * Beginne Suche nach Dateien.
     *
     */
    private void startProcess() {
        statusLab.setText( "Starte Suche..." );
        busyLab.setBusy( true );
        fsw = new FileScanWorker( searchField.getText() );
        globalStatPBar.setIndeterminate( true );
        fsw.execute();
    }

    private class FileScanWorker extends SwingWorker<Integer, String> {

        // Betreut einen Threadpool, welcher bei Bedarf erweitert wird
        private ExecutorService es;
        // Pfad zu Verzeichnis, den wir untersuchen wollen
        private String rootPath;

        private FileScanWorker( String rootPath ) {
            this.rootPath = rootPath;
            // Erzeuge einen cached thread pool - neue Threads werden bei Bedarf erzeugt
            es = Executors.newCachedThreadPool();
        }

        /**
         * Hier wird der code geschrieben, der nebenlauefig im EDT
         * aktualisiert werden soll. Die Aktualsierungen der GUI
         * ruft der FileCounter selber auf, ueber Umwege.
         * Siehe doPublish von MainFrame und vom Worker.
         *
         * @return
         * @throws Exception
         */
        @Override
        protected Integer doInBackground() throws Exception {
            int count = 0;
            // Erzeuge einen "ersten FileCounter"
            FileCounter fc = new FileCounter( rootPath, es, FileSystemView.
                    getFileSystemView(), MainFrame.this );
            // Uebergib diesen nun an den ExecutorService
            Future<Integer> result = es.submit( fc );
            // das Ergebnis der parallelen Ausfuehrung wird bei Beedigung aller Threads abgeholt
            count = result.get();
            // Ende der Zaehlungen, setze progressbar zurueck
            globalStatPBar.setIndeterminate( false );
            // Gib Meldung aus
            statusLab.setText( "Suche beendet : " );
            busyLab.setBusy( false );
            resultLab.setText( "Es wurden " + count + " Dateien in \"" + searchField.
                    getText() + "\"gefunden" );

            return count;
        }

        /**
         * Was soll zwischenzeitlich geschehen
         *
         * @param chunks
         */
        @Override
        protected void process( List<String> chunks ) {

            for ( String message : chunks ) {
                folderLab.setText( message );

                processArea.append( message );
                processArea.append( "\n" );
            }
        }

        /**
         * doPublish fuer den Worker ...
         *
         *
         * @param processInfo
         */
        private synchronized void doPublish( String processInfo ) {

            publish( processInfo );
        }
    }
}



Und hier der Dateizähler

Code: Alles auswählen

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package concurrency.multithreading;

import concurrency.gui.MainFrame;
import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.swing.filechooser.FileSystemView;

/**
 *
 * @author Olek77
 */
public class FileCounter implements Callable<Integer> {
    // Datei in der gesucht werden soll

    private String rootPath;
    // Executor Service - effiziente Erzeugung von mehreren Threads
    private ExecutorService es;
    // FileSystemView - schaue ob wir Verzeichnis untersuchen duerfen
    // deklariere volatile - Eventuell deadlock sonst?
    private volatile FileSystemView fsv;
    // MainFrame brauchen wir um den SwingWorker zu benutzen
    private MainFrame mf;

    /**
     *
     * @param rootPath
     * @param es
     * @param fsv
     * @param sw
     */
    public FileCounter( String rootPath, ExecutorService es, FileSystemView fsv, MainFrame mf ) {
        this.rootPath = rootPath;
        this.es = es;
        this.fsv = fsv;
        this.mf = mf;
    }

    /**
     * Ueberschreibe call()
     *
     * Falls Verzeichnis : Starte neuen FileCounter auf diesem Verzeichnis
     * Falls Datei : Zähle eins weiter
     *
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        int count = 0;

        try {
            final File f = new File( rootPath );

            if ( f != null && f.exists() && f.canRead() && f.isDirectory() ) {

                mf.doPublish(
                        "Scanne Folder : \"" + f.getAbsolutePath() + "\"" );

                // Liste von Future Objekten, welches alle Teilresultate aufnimmt
                ArrayList<Future<Integer>> resultsL = new ArrayList<Future<Integer>>();

                File[] childrens = f.listFiles();
                if ( childrens != null ) {
                    for ( File tempFile : childrens ) {
                        if ( tempFile.isDirectory() && fsv.isTraversable(
                                tempFile ) ) {
                            // Neuer FileCounter
                            FileCounter fc = new FileCounter( tempFile.
                                    getAbsolutePath(), es, fsv, mf );
                            // Uebergib den FileCounter dem ExecutorService
                            Future<Integer> resultF = es.submit( fc );
                            // Fuege das Future welches spaeter bereitsteht der Liste hinzu
                            resultsL.add( resultF );
                        } else {
                            count++;
                        }
                    }
                    // Nachdem die einzelnen Berechnungen fertig sind
                    for ( Future<Integer> filesCounted : resultsL ) {
                        try {
                            // Addiere die Ergebnisse der einzelnen Verzeichnisse
                            count += filesCounted.get();
                        } catch ( ExecutionException ee ) {
                            ee.printStackTrace();
                        }
                    }
                }
            }
        } catch ( InterruptedException ie ) {
            ie.printStackTrace();
        }

        return count;
    }
}



Ihr solltet Java ab der Version 1.6 verwenden damit es funktioniert.

Viele Grüße,

Olek
- Netbeans Certified Associate -

Benutzeravatar
smurfi
Site Admin
Beiträge: 1615
Registriert: 29.06.2006, 11:33
Wohnort: Wuppertal
Kontaktdaten:

Re: Concurrency Demo

Beitragvon smurfi » 18.01.2011, 05:34

Danke,

gutes Beispiel.....

Gruß
Michael

Benutzeravatar
nigjo
Beiträge: 584
Registriert: 08.09.2009, 09:43
Wohnort: Aachen
Kontaktdaten:

Re: Concurrency Demo

Beitragvon nigjo » 18.01.2011, 08:29

Hi Olek

Zunächst einmal Danke für deine Mühe ein solches Beispiel zu erstellen. Allerdings habe ich zwei Dinge, die mich an deinem Beispiel "stören":
  1. Du verwendest javax Klassen. Auch wenn du die URL zur Bibliothek angegeben hast (pluspunkt dafür) sollte ein Standardbeispiel sollte keine "externen" Bibliotheken nutzen.
  2. Irgendwas scheint mit dem erstellen der Threads nicht ganz zu stimmen. Wenn ich bei mir ein "größeres" Verzeichnis (z.b. meine RCP Anwendung, >20k Ordner) einlesen möchte werden läuft der Threadpool voll und ich bekomme ein OutOfMemoryError. Stelle ich den Executor auf einen "fixedThreadPool(10)" um, so hängt das Programm schon bei "kleinen" Verzeichnissen (z.B. dieses Beispiel, ~20 Ordner bei mir). Kann es sein, dass die gestarteten Threads nicht ordentlich beendet werden?
Gruß
Jens
Man sollte seine Werkzeuge kennen. Ansonsten haut man sich mit dem Hammer nur auf die Finger.

--
NetBeans Certified Engineer - Java Getriebe

Benutzeravatar
Olek77
Beiträge: 669
Registriert: 21.03.2009, 13:09

Re: Concurrency Demo

Beitragvon Olek77 » 19.01.2011, 14:32

Hallo,

Danke für die Hinweise.

Also SwingX-Komponenten kann man ja auch durch die jeweiligen Swing-Pendants ersetzen, bis auf JXBusyLabel, wobei dieses
eh nur Schnickschnack ist und den ruckelfreien Ablauf zeigen soll. Ansonsten muss man halt das Schließverhalten vom JFrame definieren.
Ich werbe aber immer wieder gerne für SwingX weil es einfach so viele wichtige Funktionalitäten beinhaltet.
Nicht nur was weitere Widgets anbelangt, sondern auch im Bereich Java2D/Painter/Bildmanipulation.

@Threas:

Hmm bei mir lief bisher alles immer bis zu Ende, allerdings unter Windows XP, wo ich z.B. Laufwerk C gescannt habe und ca. 60k Dateien
gefunden werden.
Dabei gab es teilweise Exceptions wenn ein Ordner ( sollte nur Systemordner ) widererwarten doch nicht gelesen werden konnte.
Ich denke es kann aber auch an einer falschen Synchronisation liegen.

Ich lese gerade das Buch "Concurrency in Practice" ( Super Buch ) und da habe ich gestern vielleicht genau darüber gelesen was bei meinem Beispiel falsch laufen könnten.

Code: Alles auswählen

 /**
   * Reiche die Zwischenergebnisse zum Worker durch. Da
   * nur aus der Klasse die den Worker erweitert publish
   * aufgerufen werden kann.
   *
   * @param processInfo
   */
   public void doPublish( String processInfo ) {
        synchronized( fsw ) {
            fsw.doPublish( processInfo );
        }
    }


Was für ein Betriebssystem verwendest du denn?

Bei einem fixed thread pool von nur 10 Threads kann es natürlich sein, dass schon nach kurzer Zeit
alle Threads belegt sind und eventuell es einen deadlock gibt wenn er eigentlich zig neue Erzeugen möchte,
aber noch keiner der laufenden Threads beendet wurde.

Letztlich ist der Punkt wo ein Thread beedet wurde, wenn der aufruf von

Code: Alles auswählen

Future.get();

ausgeführt wurde.

Ganz durchschaue ich das aber leider auch nicht. Vielleicht wäre ein einfacheres Beispiel auch besser debugbar gewesen;)

Gruß,
Olek
- Netbeans Certified Associate -

kleopatra
Beiträge: 2
Registriert: 14.09.2010, 08:54

Re: Concurrency Demo

Beitragvon kleopatra » 27.01.2011, 11:23

Olek77 hat geschrieben:Hallo,

Ich werbe aber immer wieder gerne für SwingX



:D :D :D

Cheers
Jeanette


Zurück zu „Java - Tutorials“

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast