1

J'ai une ConcurrentHashMap et une méthode qui met une chaîne dans la carte, puis je fais quelques actions dans un bloc synchronisé basé sur la valeur insérée.ConcurrentHashMap putIfAbsent première fois

putIfAbsentrenvoie la valeur précédente associée à la clé spécifiée, ou null s'il n'y avait pas de correspondance pour la clé - en fonction de la documentation officielle

Il y a 2 actions qui sont exécutées selon que putIfAbsent renvoie la valeur null ou pas.

Maintenant, voici l'astuce. Je souhaite que la première action (lorsque putIfAbsent renvoie null) soit exécutée en premier et que tous les autres threads soient mis en attente. Mon code fonctionne comme prévu dans 95% des cas.

private final ConcurrentHashMap<String, String> logins = new ConcurrentHashMap<>(); 

public void login(String id){ 
     String inserted=logins.putIfAbsent(id,id); 

     synchronized(logins.get(id)){ 
      if(inserted==null){ 
       System.out.println("First login"); 
      }else{ 
       System.out.println("Second login"); 
      }   
     } 
} 

Si j'appelle cette méthode avec la même valeur de chaîne de fils différents login("some_id"); parfois (environ 5% du temps) je reçois ce message sur la console:

Second login 
First login 

Que dois-je changer être toujours sûr que First login est exécuté en premier?

Mise à jour: D'après ce que j'ai lu est-il possible que logins.get (id) renvoie null, donc la synchronisation sur un objet nul?

+0

logins.putIfAbsent (id, id), et vos instructions de bloc synchronisées ne sont pas atomiques. C'est pourquoi parfois le deuxième login est exécuté en premier.Aussi, il n'est pas bon de synchroniser sur les littéraux de chaîne –

+0

Est-ce que 'map' devrait être' logins'? –

+0

@MichaelEaster oui. désolé j'ai modifié le code –

Répondre

0
private final ConcurrentHashMap<String, String> logins= new ConcurrentHashMap<>(); 
private ConcurrentHashMap<String, Object> locks= new ConcurrentHashMap<>(); 


public void login(String id){ 

locks.putIfAbsent(id,new Object()); 
Object lock = locks.get(id); 
synchronized(lock) 
{ 
     String inserted=logins.putIfAbsent(id,id); 

      if(inserted==null){ 
       System.out.println("First login"); 
      }else{ 
       System.out.println("Second login"); 
      }   

} 
} 

Note: Assurez-vous également que vous supprimez les entrées de hashmaps lorsque l'ID est supprimé

ou utiliser pour synchroniser le code

0

parfois un autre champ (en dehors de string id) (environ 5% du temps) Je reçois ce message sur la console:

Vous avez une condition de concurrence que le premier à ajouter n'est pas le premier à imprimer.

Dans ce cas, votre principal goulet d'étranglement est l'utilisation de System.out, il s'agit d'une ressource contente beaucoup plus coûteuse que l'utilisation d'une carte, concurrente ou non.

Dans ce cas, vous pouvez aussi bien simplifier votre code et vous obtenez un seul verrou qui est le verrou sur System.out que vous devez obtenir de toute façon

// use System.out as lock so logging of actions is always in order. 
private final Set<String> ids = Collections.newSetFromMap(new HashMap<>()); 

public void login(String id) { 
    synchronized (System.out) { 
     System.out.println(ids.add(id) ? "First login" : "Second login")l 
    } 
} 
0

Java offre d'autres mécanismes de synchronisation qui offrent une plus grande granularité , et OMI, la clarté.

Considérons le code ci-dessous. Le code illustre (a) comment protéger plusieurs opérations avec un verrou (b) comment les sections then et else peuvent être traitées différemment (par exemple then protège les fonctions avec le verrou; else suppose que les fonctions ne nécessitent pas de protection. situation):

class Task implements Runnable { 
    private String id; 
    private ConcurrentHashMap<String,String> logins; 
    private Lock lock; 

    public Task(String id, ConcurrentHashMap<String,String> logins, Lock lock) { 
     this.id = id; 
     this.logins = logins; 
     this.lock = lock; 
    } 

    public void run() { 
     login(id); 
    } 

    public void login(String id){ 
     lock.lock(); 

     String inserted = logins.putIfAbsent(id,id); 

     if (inserted==null) { 
      System.out.print("First login "); 
      // other functions that require synchronization 
      lock.unlock(); 
     } else { 
      lock.unlock(); 
      // functions that do NOT require synchronization 
      System.out.print("Second login "); 
     }   
    } 
}