JAVA Synchronisierung(Threads)

satix500

Lieutenant
Registriert
Apr. 2007
Beiträge
513
Hi,

Ich habe folgendes Problem: Ich möchte auf eine Methode mit mehreren Threads gleichzeitig zugreifen lassen, dazu brauche ich bestimmte Variablen(Counter) zu synchronisieren.
Code:
public class Sync()
{
   private int count=0;
   public synchronized void increment()
   { count++;}
    public synchronized void decrement()
   { count--;}
   public synchronized int value()
   { return count;}
}

public class MyClass()
{
   private static final counter = new Sync();

   private void myMethod(){
   counter.increment();
   System.out.println("value: " + counter.value());
   }
}
Ich würde erwarten, dass bei 10 Threads:
value: 1
value: 2
....
value: 10

ausgegeben würde.

Ich bekomme leider manche Zahlen doppelt ausgegeben, andere dafür überhaupt nicht:
value: 2
value: 2
value: 3
value: 4
....

Was läuft hier falsch???
 
Zuletzt bearbeitet:
Hallo,

ich denke, das Problem ist, dass die Methode increment durch das synchronized nur innerhalb eines Objektes geschützt ist. Also wenn du jetzt in jedem Thread mit demselben Sync-Objekt die increment-Methode aufrufst, sollte es eigentlich so klappen.
Wenn du aber nun mehrere Objekte erschaffst (was du wahrscheinlich beim Erstellen der 10 Threads machst) ist dies nicht mehr gewährleistet.

Grüße und ich hoffe das hilft dir weiter
Daniel
 
Die Klasse MyClass() hat einen Klassenkonstruktor, der als Thread-Safe-Singleton realisiert ist, somit ist es nicht möglich mehr als ein Objekt von MyClass() zu erzeugen.
Und wie du schon sagtest, so sollte es klappen, ist aber nicht der Fall:(
 
Könntest du vielleicht nochmal etwas mehr Code posten :)

Edit:

Wichtig ist, dass das Objekt, welches erhöht werden soll, dasselbe ist (dieselbe Instanz also), die bei allen Threads verwendet wird. Wenn du einfach mehrere Threads startest und jeder sein eigenen integer-Counter erstellt, dann bringt auch das synchronisieren nichts:

Code:
private int count=0;

Damit erschaffst du immer in jedem Object ein neues counter-Objekt.

Gebe zum Beispiel beim Thread-Erstellen das counter-Objekt mit und mache den Aufruf von dem increment in einem synchronized-Block.
 
Zuletzt bearbeitet: (Noch was dazu)
Code:
/** Thread-Safe Singleton Constructor */
	static final private MyClass mycls;
	static{
		try{
			mycls = new MyClass();
		}
		catch(Throwable e){
		   throw new RuntimeException(e.getMessage());
		}
	}
	
	private MyClass() {}
	
	static public MyClass getInstance()
	{
		return mycls;
	}
                


 
                public class Testclass()
                {
                    private MyClass myclsObject = MyClass.getInstance();
                     myclsObject.myMethod();
                }
 
Zuletzt bearbeitet:
Ja das mit dem Singleton ist schon klar. Es geht aber nicht darum, dass du nur ein Objekt von MyClass erstellst, sondern alle Threads auf ein gemeinsames Objekt (was dann halt synchronisiert werden muss) zugreifen.
Jetzt ist es so, dass jeder Thread seinen eigenen Counter besitzt.

PS: Benenne deine Klassen und Variablen richtig, benutzt CamelCase und auch eine einheitliche Formatierung macht den Programmierstil besser :)
 
Naja ganz oben in deinem ersten Post erstellst du doch beim Erstellen eines Objektes der Klasse Sync jedesmal mit

Code:
  private int count=0;

die Variable neu.

Ich seh halt aus deinen Code-Schnipseln echt auch noch nicht, wie du mehrere Threads erzeugst und dein Code sieht sehr wirr aus.
 
In den Java-Foren von Sun selbst gibt es die Regelung, dass man einen vollständig compilierbaren (!) Quellcode vorlegen muss. Sollte man hier auch mal einführen. Die uns hier vorgeworfenen Codeteile sagen jedenfalls 0 aus.
 
Kann dem bisherigen nur zustimmen,aber es gibt zu deinem "problem"auch einen sichere lösung:


Code:
public class Sync() {
   private static int count=0;
   private static final Object syncLock = new Object();
   public void increment()  { 
           synchronized (syncLock) {
                 count++;
            }
   }
    public  void decrement(){
           synchronized (syncLock) {
                 count++;
            }
   public  int value()
   { return count;}
}

Erst mal die variable count static machen. Dann wird immer die selbe verwendet.

Dann solltest du nicht den methoden aufruf syncronized sondern den Inhalt. Sonst betrifft das synchronize eben nur je eine Instanz.

Jo :)

nox
 
jetzt aber:

Code:
public class Sync()
{
   private int count=0;
   public synchronized void increment()
   { count++;}
    public synchronized void decrement()
   { count--;}
   public synchronized int value()
   { return count;}
}

public class MyClass()
{
   private static final Sync counter = new Sync();
   
   /** Thread-Safe Singleton Constructor */
	static final private MyClass mycls;
	static
	{
	     try
	    {
	        mycls = new MyClass();
	    }
	    catch(Throwable e)
	    {
	       throw new RuntimeException(e.getMessage());
	    }
	}
	
	private MyClass() {}
	
	static public MyClass getInstance()
	{
	    return mycls;
	}


   public void myMethod()
   {
      counter.increment();
      System.out.println("value: " + counter.value());
   }
}


public class MyThread extends Thread()
{
    String name;
    private MyClass myclsObject = MyClass.getInstance();
     
    public void run()
    {	
        myclsObject.myMethod();
    }  
}

public class ThreadCreator()
{
   Thread t1 = new MyThread("Thread1");
   Thread t2 = new MyThread("Thread2");
   t1.start();
   t2.start();
}
 
Zuletzt bearbeitet:
Nö auch das ist sicher kein funktionierendes Beispiel. Denn so läuft das sicher nicht:
Code:
Thread t1 = new Thread("Thread1");
Das ist ein allgemeiner Thread, der gar nichts macht.

Aber ich hab dein Problem gefunden.
Code:
public void myMethod()
   {
      counter.increment();
      System.out.println("value: " + counter.value());
   }
Das müsste synchronized sein bzw. am Besten lässt du dir von der Funktion counter.increment() einfach den neuen Wert direkt zurückgeben! Vor dem Aufruf von counter.value() kann ein anderer Thread schon increment() aufgerufen haben und dadurch kommt genau dein beobachteter Effekt zustande.
 
Zuletzt bearbeitet:
Das müsste synchronized sein bzw. am Besten lässt du dir von der Funktion counter.increment() einfach den neuen Wert direkt zurückgeben!
Was ist im Fall von zweifach oder mehrfach den Wert zurückgeben?:
Code:
public void myMethod()
   {
      counter.increment();
      System.out.println("value: " + counter.value());
      System.out.println("value: " + counter.value());
   }

Vor dem Aufruf von counter.value() kann ein anderer Thread schon increment() aufgerufen haben und dadurch kommt genau dein beobachteter Effekt zustande.
Wieso denn das? Wozu habe ich denn synchronized increment, decrement oder value() erstellt, wenn ein anderer Thread zwischendurch was ändern kann/darf. Das verstehe ich eben nicht.
 
Du rufst counter.increment() auf. Das ist synchronized, ja. Aber wenn Thread 1 er aus der Funktion in deinem myMethod() zurückkommt, ist es eben nicht mehr synchronized. Dementsprechend kann es genau dann passieren, dass ein anderer Thread (Thread 2) arbeiten darf. Irgendwann (wenn das Betriebssystem wieder Thread1 Rechenleistung zuteilt) bissel später wird dann erst das System.out.println() von Thread1 aufgerufen und da hat sich dann in der Zwischenzeit der Wert natürlich schon geändert!

Wenn du das mehrfach ausgibst wirds nur noch mehr undefiniert ;)
Du bräuchtest wie gesagt:
Code:
public synchronized void increment()
  count++;
return count;}

...
public void myMethod(){
System.out.println(counter.increment());
}
 
Zuletzt bearbeitet:
@BernieG
Jetzt verstehe ich zumindest, wie es zu diesem Phantom Read kommt.

@TheNoxier
Deine sichere Lösung scheint nicht zu funktionieren.
 
Das zusätzlich in der Lösung von TheNoxier auftretenden Problem ist, dass die Variable count korrekterweise als "volatile" deklariert werden müsste (natürlich löst es dein anderes Problem das ich gefunden habe auch nicht!). Andernfalls arbeiten die verschiedenen Threads u.U. mit unterschiedlichen Kopien und es kommt zu seltsamen Effekten.
Erklärung zu volatile:
Mithilfe des Schlüsselworts volatile lässt sich der Zugriff auf eine Variable synchronisieren. Ist eine Variable als volatile deklariert, muss die JVM sicherstellen, dass alle zugreifenden Threads ihre Kopien aktualisieren, sobald die Variable geändert wird.
Ein Feld kann als volatile deklariert werden. In diesem Fall muss ein Thread seine Arbeitskopie des Feldes jedes Mal, wenn er auf die Variable zugreift, mit der Masterkopie abgleichen.

Was für dich auch interessant sein könnte wäre die Verwendung eines AtomicInteger: http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/atomic/AtomicInteger.html Damit kannst du die ganze Synchronisierung sparen.
 
Hab die myMethod jetzt erst gesehen... dann hilft mein code natürlich auch nichts :)


volatile -> geile Sache!

Das wusst ich ja noch gar nicht .. deshalb wohl:

It's probably fair to say that on the whole, the volatile keyword in Java is poorly documented, poorly understood, and rarely used. To make matters worse, its formal definition actually changed as of Java 5. Essentially, volatile is used to indicate that a variable's value will be modified by different threads.
http://www.javamex.com/tutorials/synchronization_volatile.shtml

Man lernt nie aus - Danke!

nox
 
Hi,
volatile und Atomic sehr interessant!!!!

Ich konnte mit beiden fast mein Ziel erreichen.Ich bekomme jetzt auf jeden Fall jede Zahl nur einmal ausgegeben, aber die Reihenfolge ist manchmal durcheinander.

Ich habe das Program um 3 weitere Threads erweitert und 2 zus. Ausgaben:
Code:
private static final AtomicInteger atomicINT = new AtomicInteger();
.....
public void myMethod()
   {
       int value = atomicINT.incrementAndGet();
       System.out.println("value1: " + value);
       System.out.println("value2: " + value);
       System.out.println("value3: " + value);
   }

Wie kann ich nun sicherstellen, dass value1 bis value3 alle den gleichen Wert zurückliefern.
Es kann hier nämlich passieren, dass man value auf 1 gesetzt hat und danach die Ausgaben der Reihe nach ausführt :value1-->1, value2-->1 value3-->2 , weil Thread 2 in der Zwischenzeit den Wert von "value" geändert hat, und nicht immer den gleichen Wert in allen 3 Ausgaben ausgegeben kriegt.
 
Thread 2 kann doch eigtl. nur den Wert in atomicINT selbst ändern aber deine (lokale) value-Variable doch nicht. Fehlt da irgendwas Größeres an Code was du nicht gepostet hast?
Hinweis: value ist (da es ein int ist) ein primitiver Datentyp und daher gilt die Aussage hier. Bei primitiven Datentypen wird nämlich der Wert komplett kopiert und nicht einfach nur eine Referenz gesetzt. Wäre value dagegen ein Objekt (z.B. in Form eines Integer), so würde natürlich nur die Referenz auf das Objekt in ihr gespeichert und ein anderer Thread könnte (wenn er auf demselben Objekt arbeitet) tatsächlich Änderungen durchführen.

Es gibt noch einige weitere interessante Sachen in Java für die Synchronisierung, schau mal insbesondere hier http://menzsoft.ch/concurrent.htm Je nachdem was du da baust (oder ist dieses Hochzählen schon dein wirkliches Ziel?) sind die Sachen dort sehr sinnvoll und ersparen jede Menge Arbeit.
 
Zuletzt bearbeitet:
Zurück
Oben