Exklusiver Zugriff auf Objekt einer Map

chickenwings

Erfahrenes Mitglied
Hallo,

ich suche derzeit intensiv nach einer Möglichkeit des exklusiven Zugriffs auf ein Objekt innerhalb einer Map.

Es soll also n Threads geben, die auf eine Map zugreifen. In dieser Map sind bestimmte Objekte enthalten. Wenn nun ein Thread sich ein Objekt herausholt, um es zu bearbeiten, soll explizit dieses Objekt für den Zugriff durch andere Threads gesperrt werden.

Ich bin mir allerdings noch nicht sicher, wie ich das steuern soll, ob es da evtl. schon Java Hausmittelchen gibt. Meine bisherige Idee wäre, dass der Thread, der sich das Objekt zum Lesen herausholt, in eine zweite Map ablegt, die nur Objekte beinhaltet, die gerade bearbeitet werden. So könnte ein anderer Thread dort nachsehen, ob es schreibbar ist oder nicht.

Hat jemand schon mal ein ähnliches Problem gelöst?

Grüsse,
chickenwings
 
Javascript:
synchronize(dein_objekt) {
    //Code, der sicher auf dem Objekt arbeiten kann, weil kein anderer Thread darauf zugreifen darf
}

Weiterführendes:
http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

http://en.wikipedia.org/wiki/Race_condition
http://en.wikipedia.org/wiki/Synchronization_(computer_science)#Thread_or_process_synchronization
http://en.wikipedia.org/wiki/Monitor_(synchronization)
http://en.wikipedia.org/wiki/Semaphore_(programming)


Edit: Um dir die Problematik nochmal deutlich zu machen:

Meine bisherige Idee wäre, dass der Thread, der sich das Objekt zum Lesen herausholt, in eine zweite Map ablegt, die nur Objekte beinhaltet, die gerade bearbeitet werden. So könnte ein anderer Thread dort nachsehen, ob es schreibbar ist oder nicht.

Das Problem ist, dass das "Nachsehen" auch synchronisiert werden müsste! Denn ansonsten können sich zwei Threads genau beim Nachsehen in die Quere kommen und am Ende denken beide, Sie könnten auf dem Objekt arbeiten.


A und B sind Threads
Code:
A prüft, ob Objekt in Liste und stellt fest, es ist nicht drin
A wird unterbrochen
B startet
B prüft, ob Objekt in Liste und stellt fest, es ist nicht drin
B fügt Objekt zu Liste hinzu
B arbeitet auf Objekt
B wird unterbrochen
A startet
A fügt Objekt zu Liste hinzu (ist jetzt doppelt drin)
A arbeitet auf Objekt (obwohl B das auch gerade tut)
 
Zuletzt bearbeitet:
Das Problem ist, dass das "Nachsehen" auch synchronisiert werden müsste! Denn ansonsten können sich zwei Threads genau beim Nachsehen in die Quere kommen und am Ende denken beide, Sie könnten auf dem Objekt arbeiten.
Ne, das sollten sie eigentlich nicht denken, weil der sync ja auf das gleiche Objekt gemacht wird.

Thread A -> map.get(key).
Thread A -> lock auf obj
Thread B -> map.get(key)
Thread B -> wartet bis obj freigegeben ist.
Thread A -> fertig, aus dem sync-block raus.
Thread B -> wird angestossen und macht weiter.

Kann man schön mit Thread.sleep und thread-dumps nachvollziehen,...

Gruss
slowy
 
Ne, das sollten sie eigentlich nicht denken, weil der sync ja auf das gleiche Objekt gemacht wird.

Thread A -> map.get(key).
Thread A -> lock auf obj
Thread B -> map.get(key)
Thread B -> wartet bis obj freigegeben ist.
Thread A -> fertig, aus dem sync-block raus.
Thread B -> wird angestossen und macht weiter.

Kann man schön mit Thread.sleep und thread-dumps nachvollziehen,...

Gruss
slowy

Aber bei seiner Idee gibt es keinen sync Block, der ist ihm ja offenbar unbekannt, sonst gäbe es diesen Thread nicht. Ich bezog mich mit der Aussage ausschließlich auf seine Idee, eine Art Semaphor selbst zu bauen, was aber ohne die passenden Mechanismen auch in die Hose geht.
 
Hallo,

Es soll also n Threads geben, die auf eine Map zugreifen. In dieser Map sind bestimmte Objekte enthalten. Wenn nun ein Thread sich ein Objekt herausholt, um es zu bearbeiten, soll explizit dieses Objekt für den Zugriff durch andere Threads gesperrt werden.

Was meinst du mit "soll für andere Threads gesperrt sein" genau? Sollen andere Threads überhaupt keinen Zugriff mehr auf das Objekt bekommen?

Oder sollen die anderen Threads zwar noch Zurgiff auf das Objekt haben aber nur noch lesend auf zugreifen können? -> Mutationen wären dann exklusiv nur von dem derzeitigen "Owner" möglich...?


Gruß Tom
 
Hi Tom,

der Lesezugriff muss nicht exklusiv sein, jedoch der Schreibzugriff. Während des Schreibens, darf das Objekt (logischerweise) auch von keinen anderen Thread gelesen werden. Das Objekt darf nur einen spezifischen Zustand haben, der bekannt ist.

Grüsse,
chickenwings
 
Hallo,

der Lesezugriff muss nicht exklusiv sein, jedoch der Schreibzugriff. Während des Schreibens, darf das Objekt (logischerweise) auch von keinen anderen Thread gelesen werden. Das Objekt darf nur einen spezifischen Zustand haben, der bekannt ist.
Hört sich ganz nach java.util.concurrent.locks.ReadWriteLock an...

Gruß Tom
 
Hallo,

suchst du vielleicht sowas in der Art?
Der LockingWrapper dient quasi als SynchronizationProxy zum Zugriff auf das BusinessObject
Java:
package de.tutorials.training;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MapGuardExample {
  public static void main(String[] args) {
    ConcurrentMap<Integer, LockingWrapper> map = new ConcurrentHashMap<Integer, LockingWrapper>();
    map.put(0, new LockingWrapper(new BusinessObject()));

    int readerCount = 5;
    int writerCount = 2;

    ExecutorService es = Executors.newFixedThreadPool(readerCount + writerCount);
    for (int i = 0; i < readerCount; i++) {
      es.submit(new Reader(map));
    }

    for (int i = 0; i < writerCount; i++) {
      es.submit(new Writer(map));
    }
  }


  static abstract class Worker implements Runnable {
    private final ConcurrentMap<Integer, LockingWrapper> map;

    public Worker(ConcurrentMap<Integer, LockingWrapper> map) {
      this.map = map;
    }

    @Override
    public void run() {
      while (true) {
        op(map.get(0));
      }
    }

    abstract void op(LockingWrapper lockingWrapper);
  }

  static class Reader extends Worker {
    public Reader(ConcurrentMap<Integer, LockingWrapper> map) {
      super(map);
    }


    @Override
    void op(LockingWrapper lockingWrapper) {
      lockingWrapper.read();
    }
  }

  static class Writer extends Worker {
    public Writer(ConcurrentMap<Integer, LockingWrapper> map) {
      super(map);
    }


    @Override
    void op(LockingWrapper lockingWrapper) {
      if (Math.random() < 0.1) { // 10% write probability
        lockingWrapper.write();
      }
    }
  }

  static class BusinessObject {
    
    private final AtomicInteger state = new AtomicInteger();
    
    public void write() {
      System.out.println(Thread.currentThread().getId() + " writes to " + this  + " " + state.incrementAndGet());
      try {
        Thread.sleep((long) (1000 * Math.random()));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }


    public void read() {
      System.out.println(Thread.currentThread().getId() + " reads from " + this + " " + state.get());
    }
    
    @Override
    public String toString() {
      return "BO@"+ System.identityHashCode(this);
    }
  }

  static class LockingWrapper {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    private final BusinessObject bo;

    public LockingWrapper(BusinessObject bo) {
      this.bo = bo;
    }


    public void write() {
      lock.writeLock().lock();
      try {
        bo.write();
      } finally {
        lock.writeLock().unlock();
      }
    }


    public void read() {
      lock.readLock().lock();
      try {
        bo.read();
      } finally {
        lock.readLock().unlock();
      }
    }
  }

}

Ausgabe:
Code:
13 reads from BO@753416466 0
10 reads from BO@753416466 0
11 reads from BO@753416466 0
9 reads from BO@753416466 0
12 reads from BO@753416466 0
9 reads from BO@753416466 0
11 reads from BO@753416466 0
10 reads from BO@753416466 0
13 reads from BO@753416466 0
13 reads from BO@753416466 0
13 reads from BO@753416466 0
13 reads from BO@753416466 0
10 reads from BO@753416466 0
11 reads from BO@753416466 0
9 reads from BO@753416466 0
12 reads from BO@753416466 0
9 reads from BO@753416466 0
11 reads from BO@753416466 0
10 reads from BO@753416466 0
13 reads from BO@753416466 0
11 reads from BO@753416466 0
9 reads from BO@753416466 0
12 reads from BO@753416466 0
14 writes to BO@753416466 1
14 writes to BO@753416466 2
14 writes to BO@753416466 3
15 writes to BO@753416466 4
15 writes to BO@753416466 5
15 writes to BO@753416466 6
15 writes to BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
13 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
10 reads from BO@753416466 7
11 reads from BO@753416466 7
13 reads from BO@753416466 7
11 reads from BO@753416466 7
12 reads from BO@753416466 7
9 reads from BO@753416466 7
10 reads from BO@753416466 7
11 reads from BO@753416466 7
13 reads from BO@753416466 7
14 writes to BO@753416466 8
14 writes to BO@753416466 9
14 writes to BO@753416466 10
15 writes to BO@753416466 11
...

Gruß Tom
 
Hi Tom,

uhi, das sieht schon mal aus, als ginge es in die richtige Richtung.
Ich will mir mal das Beispiel zu Gemüte führen, ist doch eine schon recht ausgewachsene Implementierung.

Vielen Dank für Deine Hinweise
chickenwings
 
Hallo,

Btw. ein viel einfacherer Ansatz wäre der, in der Map nur Immutable Business Objects abzulegen. Will man einen Map-Eintrag verändern, so muss man eine neues (geändertes) Business Object an der stelle in die ConcurrentMap via map.replace(key, old,new) bzw. map.replace(key,new) einfügen. Die replace Operation verläuft dabei atomar. Deshalb gibt es da auch keine Probleme wenn mehrere Threads konkurrierend auf die Elemete der Map zugreifen.
Siehe: http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentMap.html#replace(K, V, V)

Gruß Tom
 
Zurück