Threads Expireable objects

Doltsche

Mitglied
Hallo Community

Die Ausgangslage für meine Problemstellung ist das Verwalten von Sessions, die nach einer bestimmten Zeit ablaufen sollen.

Zu diesem Zweck habe ich mir ein Konzept mit Expireable Objekten ausgedacht. Diese definieren ein bestimmtes Ablaufdatum.

Zum Verwalten dieser Objekte kommt eine Expireable List ins Spiel. Diese kapselt eine Endlos-Schleife in einem separaten Thread, der diese Expireable Objekte alle paar Sekunden durchläuft und abgelaufene Objekte aus der Liste entfernt.

An dieser Stelle ist meine Frage, ob es ausgehend von der Ausgangslage einen besseren Ansatz gibt? Wenn nein, was schlagt ihr mir vor, um einen konkurrenten Zugriff auf die Liste zu vermeiden? Denn diese wird ja alle paar Sekunden durch einen separaten Thread verwendet und gegebenfalls modifiziert.

Beste Grüsse

Samuel
 
N

nillehammer

Gast
Es kommt ein bischen drauf an, wieviele von diesen -Session-Objekten du in der Liste hast. Bei wenigen ist es egal, aber bei vielen ist es ungünstig, eine Liste zu nehmen, weil Du immer stumpf durch die Liste iterieren musst, bis Du eine expired Session gefunden hast. Hier ist ein SortedSet sortiert nach expiration-Date evtl. besser. Wobei dann sichergestellt sein muss, dass es niemals zwei gleiche Dates gibt, weil dann ein Session-Objekt nicht geaddet werden könnte. Da kann man evtl. mit einem weiteren Idenfifikations-/Sortiermerkmal für Eindeutigkeit sorgen.

[EDIT]
Oder ändert sich das expiration-Date während der Lebenszeit einer Session vielleicht? Dann müsste ich nochmal neu nachdenken...
[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:

Doltsche

Mitglied
Oder ändert sich das expiration-Date während der Lebenszeit einer Session vielleicht? Dann müsste ich nochmal neu nachdenken...

Darüber habe ich noch gar nicht nachgedacht. Ja, es sollte änderbar sein. Nun gut, dass sollte keine zu grosse Rolle spielen. Bei einem neuen Eintrag wird er einfach an der entsprechenden Stelle in der Liste eingefügt ohne, dass die ganze Liste neu sortiert werden muss.

In der Zwischenzeit ist mir hinsichtlich der Problemstellung zur gewinnung von Performance noch eine ganze andere Lösung eingefallen: Anstelle die Liste in einer Endlos-Schleife fortlaufend zu prüfen reicht es evtl. auch, diese nur beim Zugriff auf expired Objects zu prüfen. Allerdings hätte dass dann bei einer grösseren Liste eine unglückliche Zugriffszeit zur Folge.
 

Doltsche

Mitglied
Während der Versuch, die Idee einer Expireable List umzusetzen ist ein Threading Problem aufgetreten, bei welchem ich derzeit etwas ratlos bin.

In einem separaten Thread werden alle Expireable Objekte in der List itertiert und diejenigen, die abgelaufen sind, entfernt. Das hat zur Folge, dass aus zwei unterschiedlichen Threads gleichzeitig auf die List zugegriffen wird. Ich habe das versucht mit dem Keyword synchronized zu lösen: Leider ohne Erfolg.

An dieser Stelle ersuche ich nun euch um Rat. Evtl. muss das ganze Design überdacht werden.

ExpireableEngine (Erbt von Thread):
Java:
public ExpireableEngine(List<T> coreList) {
    _run = true;
    _coreList = coreList; // List mit Expireable Objekten, die verwaltet werden sollen.
}

@Override
public void run() {
    while (_run) {
        try {
            for (T obj : _coreList) { // iteriere alle expireable objects
                if (!obj.validate()) { // ist objekt abgelaufen?
                    synchronized (_coreList) { // synchronisiere Zugriff auf list
                        _coreList.remove(obj); // ConcurrentModificationException
                    }
                }
            }
            sleep(TIMEOUT);
        } catch (InterruptedException e) {
            // ...
        }
    }
}

ExpireableList (Erbt von ArrayList):
Java:
private ExpireableEngine<T> _engine = null;
    
public ExpireableList(){
    _engine = new ExpireableEngine<>(this); // erbt von ArrayList
    _engine.start(); // starte thread in der ExpireableEngine
}

Ein ExpireableObjekt umfasst ein ExpireDate. Die Methode validate() gibt false zurück, wenn das aktuelle Datum das ExpireDate überschreitet.

Freundliche Grüsse

Samuel
 
Zuletzt bearbeitet:
N

nillehammer

Gast
Das ist hier garnicht die Ursache Deines Problems. Du darfst während einer Iteration über Listenelemente kein remove auf der Liste aufrufen. Du musst mit Hilfe eines Iterators über die Elemente iterieren und mittels dessen remove-Methode Elemente löschen. Der Code dafür sieht dann ungefähr so aus (hier mal am Beispiel einer Integer-List, weil ich die Typen in Deinem Code nicht erkannt habe).
Java:
for (final Iterator<Integer> iter = intList.iterator(); iter.hasNext();) {
	final Integer current = iter.next();
		
	if (current.equals(3))
		iter.remove();
	}
}

Abgesehen davon Dein Gedanke ist richtig, aber schlecht umgesetzt. Du musst dafür sorgen, dass folgende Sachen nicht gleichzeitig passieren:
- Ein Thread iteriert über die Liste.
- Gleichzeitig schreibt ein anderer Thread in die Liste rein.
D.h. die Sperre muss irgendwo außerhalb der Iteration gesetzt werden. Du musst die Liste nämlich für den gesamten Zeitraum der Iteration gegen konkurrierende Änderungen schützen.
 
Zuletzt bearbeitet von einem Moderator:
N

nillehammer

Gast
Abgesehen davon, dass der Code so nicht funktioniert, ist Deine for-Schleife sehr unglücklich programmiert. Sie führt nämlich dazu, dass zwei Mal durch die Liste iteriert wird. Einmal aufgrund Deines Codes und das zweite Mal beim
Code:
remove(obj)
. Du zwingst Dein Programm damit nach nämlich einem Element in der Liste zu suchen. Da eine Liste dafür nicht optimiert ist, wird einfach wieder stumpf vom ersten Element aus durchiteriert und auf jedem Element
Code:
equals()
aufgerufen.

Zugriff auf Listenenlemente gerade bei größeren Listen sollten nur über den Index erfolgen. Dafür ist eine Liste da (Eine Collection, die die Reihenfolge der Elemente garantiert und über einen Index zugreifbar macht). Wenn Du Objekte suchen/finden willst, sind Sets besser geeignet. Dessen Implementierungen haben nämlich ausgeklügelte Mechanismen, um schneller ein gewünschtes Element zu finden als die stumpfe Iteration vom Anfang aus.
 
Zuletzt bearbeitet von einem Moderator:
S

Spacerat

Gast
Du brauchst keinen zusätzlichen Thread. Solche Geschichten bekommt man onDemand hin. Ich nehme mal stark an, dass es um irgendwelche LoggedIn-Aktivitäten geht und dass User nach einer gewissen Zeit der Inaktivität ausgelogged werden sollen, also das Expirable, aus der List genommen wird. Preisfrage: Was musst du tun, um die Daten der Session mit den Request-Daten des Users zu vergleichen? Richtig, du musst Das Expirable-Object finden, weil du ja mindestens den User autentifizieren und die lastActivity neu setzen musst. Mit deiner Methode findet man es wahrscheinlich nicht mehr, weils irgend ein Thread bereits entsorgt hat. Bei meiner Methode findet man es auf jeden Fall und wenn man es dann einmal hat kann man alles gleichzeitig prüfen bzw. aktualisieren. Benutzername und Passwort, lastActivity-Zeit... Oooops die's ja längst abgelaufen. Na denn mal weg mit dem Expirable und dem Benutzer mitteilen, dass er sich neu einloggen darf.
Und wenn's doch ein Thread werden soll... Innerhalb des Threads halt über eine Kopie der Liste iterieren und bei Bedarf Elemente aus der Originalen entfernen.
 
Zuletzt bearbeitet von einem Moderator:

Doltsche

Mitglied
Hallo

Besten Dank für eure Antworten =).

@Spacerat: Um einen Thread werde ich wohl kaum herum kommen. Die Basisidee hinter dem Ganzen ist der bekannte Garbage Collector. D.h. ich kann ein Expireable Object nicht nur dann prüfen wenn ich ein Request eines entsprechenden Users erhalte. Es ist ja möglich, dass sich dieser gar nicht mehr meldet. Das hätte zur Folge, dass das Objekt liegen bleibt.

Oder liege ich da falsch?

Gruss Samuel
 
S

Spacerat

Gast
Nö, da liegst du richtig. Aber ein Busy Waiting GC? Auf gar keinen Fall! Gang und gebe ist's, nach jeder Anfrage einen Timer mit MAX_LIFE_TIME zu setzen, eine Zeit, nach der definitiv alle Sessionsdateien ungültig sind und erst dann startest du deinen GC - in diesem werden dann eigentlich nur noch ein Set (SortedSet<SessionData> und eine Map (SortedMap<Long, Set<SessionData>>) gecleared.
Die Map lässt sich bei jedem Zugriff ganz fix aktualisieren: Remove headUp currentTime - MAX_LIFE_TIME. Bei der Wahl der richtigen Map (Linked), wird dieser Teil dann einfach abgehängt und dem Wohlwollen Hades überlassen statt das jedes Element einzeln gelöscht wird.
Schau's dir mal an
Java:
public final class SessionManager
{
	private static final long MAX_LIFE_TIME = 100L; //1800000L;

	private final SessionData syncObject = new SessionData();
	private final Timer sessionGC = new Timer("sessionGC", true);
	private TimerTask runGC;
	private final SortedSet<SessionData> sessions = new TreeSet<SessionData>();
	private final SortedMap<Long, Set<SessionData>> expires = new TreeMap<Long, Set<SessionData>>();

	private SessionManager()
	{
	}

	public SessionData get(long sessionID)
	{
		long expiredTime = System.currentTimeMillis() - MAX_LIFE_TIME;
		synchronized(syncObject) {
			syncObject.id = sessionID;
			if(runGC != null) {
				runGC.cancel();
			}
			runGC = new TimerTask()
			{
				@Override
				public void run()
				{
					sessions.clear();
					expires.clear();
					System.out.println("no more Sessions");
				}
			};
			sessionGC.purge();
			sessionGC.schedule(runGC, MAX_LIFE_TIME);
			Map<Long, Set<SessionData>> removals = expires.headMap(expiredTime);
			for(Entry<Long, Set<SessionData>> e : removals.entrySet()) {
				sessions.removeAll(e.getValue());
			}
			removals.clear();
			SortedSet<SessionData> tail = sessions.tailSet(syncObject);
			SessionData rc = (!tail.isEmpty())? tail.first() : null;
			if(rc != null && rc.getId() == sessionID) {
				expires.get(rc.lastActivity).remove(rc);
				rc.lastActivity = System.currentTimeMillis();
				if(!expires.containsKey(rc.lastActivity)) {
					expires.put(rc.lastActivity, new TreeSet<SessionData>());
				}
				expires.get(rc.lastActivity).add(rc);
				return rc;
			}
			return null;
		}
	}

	public SessionData create()
	{
		SessionData rc = new SessionData();
		if(!expires.containsKey(rc.lastActivity)) {
			expires.put(rc.lastActivity, new TreeSet<SessionData>());
		}
		expires.get(rc.lastActivity).add(rc);
		sessions.add(rc);
		return rc;
	}

	public static void main(String[] args)
	throws Throwable
	{
		SessionManager sessionManager = new SessionManager();
		SessionData[] sessions = new SessionData[10];
		for(int n = 0; n < 10; n++) {
			sessions[n] = sessionManager.create();
			Thread.sleep((long)(Math.random() * 10.0));
		}

		for(int n = 0; n < 1000; n++) {
			int i = (int) (Math.random() * 10.0);
			if((sessions[i] = sessionManager.get(sessions[i].getId())) == null) {
				System.out.println("Session " + i + " expired");
				sessions[i] = sessionManager.create();
			} else {
				System.out.println("Session " + i + " lastActivity " + sessions[i].getLastActivity());
			}
			Thread.sleep((long)(Math.random() * 40.0));
		}
		Thread.sleep(MAX_LIFE_TIME * 2);
	}
}

// Dummy Class
class SessionData
implements Comparable<SessionData>
{
	private static long IDCNT = System.currentTimeMillis();

	long id;
	long lastActivity;

	SessionData()
	{
		synchronized(SessionData.class) {
			lastActivity = System.currentTimeMillis();
			id = (IDCNT++) * 31 + lastActivity;
		}
	}

	public long getLastActivity()
	{
		return lastActivity;
	}

	public long getId()
	{
		return id;
	}

	@Override
	public int compareTo(SessionData o)
	{
		return (int) (id - o.id);
	}
}
Schöner wäre es in dem Beispiel natürlich gewesen, wenn man zwei mehr gestellte Anfragen gleichzeitig emulieren könnte... aber das war mir dann doch zu viel. ;)
 
Zuletzt bearbeitet von einem Moderator:
M

Marcinek

Gast
Schau dir mal an, wie jboss Cache das macht.. da werden prinzipiell die nodes eines Baums weggehackt.
 

Ähnliche Java Themen


Oben