Wie synchronisiere ich eine statische Variable zwischen Threads, auf denen verschiedene Instanzen einer Klasse in Java ausgeführt werden?

120

Ich weiß, dass die Verwendung des synchronizeSchlüsselworts vor einer Methode die Synchronisation für dieses Objekt bewirkt. Das heißt, 2 Threads, auf denen dieselbe Instanz des Objekts ausgeführt wird, werden synchronisiert.

Da sich die Synchronisation jedoch auf Objektebene befindet, werden 2 Threads, auf denen verschiedene Instanzen des Objekts ausgeführt werden, nicht synchronisiert. Wenn wir eine statische Variable in einer Java-Klasse haben, die von der Methode aufgerufen wird, möchten wir, dass sie über Instanzen der Klasse hinweg synchronisiert wird. Die beiden Instanzen werden in zwei verschiedenen Threads ausgeführt.

Können wir die Synchronisation auf folgende Weise erreichen?

public class Test  
{  
   private static int count = 0;  
   private static final Object lock= new Object();    
   public synchronized void foo() 
  {  
      synchronized(lock)
     {  
         count++;  
     }  
  }  
}

Stimmt es, dass die statische Variable jetzt über Klasseninstanzen hinweg synchronisiert ist, da wir ein lockstatisches Objekt definiert haben und das Schlüsselwort synchronizedfür diese Sperre verwenden ?countTest

Anonym
quelle
4
Alle diese Antworten sind USELESS, es sei denn, das Sperrobjekt wird als FINAL deklariert!
Schauen Sie sich auch java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Antworten:

193

Es gibt verschiedene Möglichkeiten, den Zugriff auf eine statische Variable zu synchronisieren.

  1. Verwenden Sie eine synchronisierte statische Methode. Dies wird auf dem Klassenobjekt synchronisiert.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Explizite Synchronisierung für das Klassenobjekt.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Synchronisieren Sie mit einem anderen statischen Objekt.

    public class Test {
        private static int count = 0;
        private static final Object countLock = new Object();
    
        public void incrementCount() {
            synchronized (countLock) {
                count++;
            }
        }
    } 

Methode 3 ist in vielen Fällen die beste, da das Sperrobjekt nicht außerhalb Ihrer Klasse verfügbar gemacht wird.

Darron
quelle
1
1. Der erste braucht nicht einmal ein Sperrobjekt, sollte es nicht das beste sein?
Baiyan Huang
4
2. Deklarieren Sie count als flüchtig, würde ebenfalls funktionieren, da flüchtig sicherstellt, dass die Variable synchronisiert ist.
Baiyan Huang
9
Der Grund Nr. 3 ist der beste, dass jedes zufällige Test.classCodebit Ihren Tag synchronisieren und möglicherweise Ihren Tag verderben kann. Außerdem wird die Klasseninitialisierung mit einer Sperre für die gehaltene Klasse ausgeführt. Wenn Sie also verrückte Klasseninitialisierer haben, können Sie sich selbst Kopfschmerzen bereiten. volatilehilft nicht, count++weil es eine Lese- / Änderungs- / Schreibsequenz ist. Wie in einer anderen Antwort erwähnt, java.util.concurrent.atomic.AtomicIntegerist hier wahrscheinlich die richtige Wahl.
Fadden
4
Vergessen Sie nicht, den Lesevorgang nach Anzahl zu synchronisieren, wenn Sie den korrekten Wert lesen möchten, der von anderen Threads festgelegt wurde. Das Deklarieren als flüchtig (zusätzlich zum synchronisierten Schreiben) hilft auch dabei.
n0rm1e
1
@Ferrybig nein, du sperrst dich ein Test.class. thiswäre die Sperre für synchronisierte nicht statische Methoden
user3237736
64

Wenn Sie lediglich einen Zähler freigeben , sollten Sie eine AtomicInteger oder eine andere geeignete Klasse aus dem Paket java.util.concurrent.atomic verwenden:

public class Test {

    private final static AtomicInteger count = new AtomicInteger(0); 

    public void foo() {  
        count.incrementAndGet();
    }  
}
Kevin
quelle
3
Es ist in Java 1.5 verfügbar, nicht in 1.6.
Pawel
4

Ja, das ist wahr.

Wenn Sie zwei Instanzen Ihrer Klasse erstellen

Test t1 = new Test();
Test t2 = new Test();

Dann synchronisieren sich t1.foo und t2.foo beide auf demselben statischen Objekt und blockieren sich daher gegenseitig.

Reichtum
quelle
einer blockiert den anderen, nicht sofort, wenn man sich darum kümmert.
Jafar Ali
0

Wir können auch ReentrantLock verwenden , um die Synchronisation für statische Variablen zu erreichen.

public class Test {

    private static int count = 0;
    private static final ReentrantLock reentrantLock = new ReentrantLock(); 
    public void foo() {  
        reentrantLock.lock();
        count = count + 1;
        reentrantLock.unlock();
    }  
}
Sunil
quelle
-1

Sie können Ihren Code über die Klasse synchronisieren. Das wäre am einfachsten.

   public class Test  
    {  
       private static int count = 0;  
       private static final Object lock= new Object();    
       public synchronized void foo() 
      {  
          synchronized(Test.class)
         {  
             count++;  
         }  
      }  
    }

Ich hoffe, Sie finden diese Antwort hilfreich.

Jafar Ali
quelle
2
Dies wird funktionieren, aber wie an anderer Stelle von @Fadden erwähnt, beachten Sie, dass jeder andere Thread ebenfalls synchronisiert werden Test.classund das Verhalten beeinflussen kann. Aus diesem Grund wird die Synchronisierung lockmöglicherweise bevorzugt.
sbk
Was Sie sagen, ist richtig. Deshalb erwähne ich deutlich, dass das oben Genannte der einfachste Ansatz ist.
Jafar Ali