Heim  >  Artikel  >  Java  >  So verwenden Sie Synchronized in Java

So verwenden Sie Synchronized in Java

高洛峰
高洛峰Original
2016-12-13 11:04:221666Durchsuche

synchronized ist ein Schlüsselwort in Java und eine Art Synchronisationssperre. Die geänderten Objekte sind wie folgt:
1. Der geänderte Codeblock wird als Synchronisationsanweisungsblock bezeichnet Objekt des Codeblocks;
2. Die geänderte Methode wird als Synchronisationsmethode bezeichnet, und das Objekt, auf das sie wirkt, ist das Objekt, das diese Methode aufruft. Ändern Sie eine statische Methode. Ihr Aktionsbereich ist die gesamte statische Methode, und ihr Ziel sind alle Objekte dieser Klasse.
4 Das Hauptobjekt ist diese Klasse aller Objekte.

Ändern Sie einen Codeblock

Wenn ein Thread auf den synchronisierten (diesen) synchronisierten Codeblock in einem Objekt zugreift, werden andere Threads, die versuchen, auf das Objekt zuzugreifen, blockiert. Schauen wir uns das folgende Beispiel an:

[Demo1]: Verwendung von synchronisiert

/**
 * 同步线程
 */class SyncThread implements Runnable {   private static int count;   public SyncThread() {
      count = 0;
   }   public  void run() {      synchronized(this) {         for (int i = 0; i < 5; i++) {            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }   public int getCount() {      return count;
   }
}
Aufruf von SyncThread:

SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
Das Ergebnis ist wie folgt:

SyncThread1:0 
SyncThread1:1 
SyncThread1:2 
SyncThread1:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread2:7 
SyncThread2:8 
SyncThread2:9*
Wenn zwei gleichzeitige Threads (Thread1 und Thread2) auf den synchronisierten Codeblock im selben Objekt (syncThread) zugreifen, kann nur ein Thread gleichzeitig ausgeführt werden, und der andere Thread ist blockiert und muss auf das warten Der aktuelle Thread muss die Ausführung beenden. Dieser Codeblock kann erst später ausgeführt werden. Thread1 und Thread2 schließen sich gegenseitig aus, da das aktuelle Objekt gesperrt ist, wenn der synchronisierte Codeblock ausgeführt wird. Die Objektsperre kann erst aufgehoben werden, nachdem der Codeblock ausgeführt wurde, und der nächste Thread kann das Objekt ausführen und sperren.

Ändern wir den Aufruf von SyncThread leicht:

Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
Die Ergebnisse sind wie folgt:

SyncThread1:0 
SyncThread2:1 
SyncThread1:2 
SyncThread2:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread1:7 
SyncThread1:8 
SyncThread2:9
Bedeutete das nicht, dass, wenn ein Thread den synchronisierten Codeblock ausführt, Andere Threads sind blockiert? Warum werden Thread1 und Thread2 im obigen Beispiel gleichzeitig ausgeführt? Dies liegt daran, dass synchronisiert nur Objekte sperrt und jedem Objekt nur eine Sperre (Sperre) zugeordnet ist und der obige Code dem folgenden Code entspricht:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
Zu diesem Zeitpunkt werden zwei SyncThreads erstellt Bei den Objekten syncThread1 und syncThread2 führt Thread Thread1 den synchronisierten Code (Ausführung) im SyncThread1-Objekt aus, und Thread Thread2 führt den synchronisierten Code (Ausführung) im SyncThread2-Objekt aus. Wir wissen, dass die Synchronisierung das Objekt sperrt und es zwei Sperren gibt das syncThread1-Objekt bzw. das syncThread2-Objekt, und diese beiden Sperren stören sich nicht gegenseitig und bilden keinen gegenseitigen Ausschluss, sodass die beiden Threads gleichzeitig ausgeführt werden können.

2. Wenn ein Thread auf einen synchronisierten (diesen) synchronisierten Codeblock eines Objekts zugreift, kann ein anderer Thread weiterhin auf den nicht synchronisierten (diesen) synchronisierten Codeblock im Objekt zugreifen.

[Demo2]: Mehrere Threads greifen auf synchronisierte und nicht synchronisierte Codeblöcke zu

class Counter implements Runnable{
   private int count;   public Counter() {      count = 0;
   }   public void countAdd() {
      synchronized(this) {         for (int i = 0; i < 5; i ++) {            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }   //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
   public void printCount() {      for (int i = 0; i < 5; i ++) {         try {
            System.out.println(Thread.currentThread().getName() + " count:" + count);
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }   public void run() {
      String threadName = Thread.currentThread().getName();      if (threadName.equals("A")) {
         countAdd();
      } else if (threadName.equals("B")) {
         printCount();
      }
   }
}
Aufrufcode:

Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();
Die Ergebnisse sind wie folgt:

A:0 
B count:1 
A:1 
B count:2 
A:2 
B count:3 
A:3 
B count:4 
A:4 
B count:5
Im obigen Code ist countAdd synchronisiert und printCount ist nicht synchronisiert. Aus den obigen Ergebnissen ist ersichtlich, dass, wenn ein Thread auf den synchronisierten Codeblock eines Objekts zugreift, andere Threads auf den nicht synchronisierten Codeblock des Objekts zugreifen können, ohne blockiert zu werden.

Angeben, um ein Objekt zu sperren

[Demo3]: Angeben, um ein Objekt zu sperren

/**
 * 银行账户类
 */class Account {
   String name;   float amount;   public Account(String name, float amount) {      this.name = name;      this.amount = amount;
   }   //存钱
   public  void deposit(float amt) {
      amount += amt;      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }   //取钱
   public  void withdraw(float amt) {
      amount -= amt;      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }   public float getBalance() {      return amount;
   }
}/**
 * 账户操作类
 */class AccountOperator implements Runnable{   private Account account;   public AccountOperator(Account account) {      this.account = account;
   }   public void run() {      synchronized (account) {
         account.deposit(500);
         account.withdraw(500);
         System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
      }
   }
}
Aufrufcode:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];for (int i = 0; i < THREAD_NUM; i ++) {
   threads[i] = new Thread(accountOperator, "Thread" + i);
   threads[i].start();
}
Die Ergebnisse sind wie folgt:

Thread3:10000.0 
Thread2:10000.0 
Thread1:10000.0 
Thread4:10000.0 
Thread0:10000.0
In der Ausführungsmethode in der AccountOperator-Klasse verwenden wir synchronisiert, um das Kontoobjekt zu sperren. Wenn zu diesem Zeitpunkt ein Thread auf das Kontoobjekt zugreift, werden andere Threads, die versuchen, auf das Kontoobjekt zuzugreifen, blockiert, bis der Thread auf das Kontoobjekt zugreift. Mit anderen Worten: Wer die Sperre erhält, kann den von ihr kontrollierten Code ausführen.

Wenn ein klares Objekt als Sperre vorhanden ist, können Sie ein Programm auf ähnliche Weise wie folgt schreiben.

public void method3(SomeObject obj)
{   //obj 锁定的对象
   synchronized(obj)
   {      // todo
   }
}
Wenn es kein explizites Objekt als Sperre gibt und Sie nur möchten, dass ein Codeabschnitt synchronisiert wird, können Sie ein spezielles Objekt erstellen, das als Sperre fungiert:

class Test implements Runnable{
   private byte[] lock = new byte[0];  // 特殊的instance变量
   public void method()
   {
      synchronized(lock) {         // todo 同步代码块
      }
   }   public void run() {

   }
}
Beschreibung: Byte-Array-Objekte mit der Länge Null sind wirtschaftlicher zu erstellen als jedes andere Objekt – sehen Sie sich den kompilierten Bytecode an: Für die Generierung eines Byte[]-Objekts mit der Länge Null sind nur 3 Opcodes erforderlich, während für Object lock = new Object() 7 Zeilen erforderlich sind Opcodes.

Ändern Sie eine Methode

Synchronized Das Ändern einer Methode ist sehr einfach, das heißt, das Hinzufügen von „synchronized“ vor der Methode, public synchronisiert void method(){//todo}, ändert die Methode und ändert einen Codeblock. Ähnlich, aber der Umfang ist unterschiedlich. Der geänderte Codeblock ist der in geschweifte Klammern eingeschlossene Umfang, während der Umfang der geänderten Methode die gesamte Funktion ist. Wenn Sie die Ausführungsmethode in [Demo1] auf die folgende Methode ändern, ist der Effekt derselbe.

*[Demo4]: synchronisiert ändert eine Methode

public synchronized void run() {   for (int i = 0; i < 5; i ++) {      try {
         System.out.println(Thread.currentThread().getName() + ":" + (count++));
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}
Synchronized wirkt auf die gesamte Methode.

Schreibmethode eins:

public synchronized void method()
{
   // todo
}
Schreibmethode zwei:

public void method(){
   synchronized(this) {
      // todo
   }}
Schreibmethode eins ändert eine Methode, Schreibmethode zwei modifiziert einen Codeblock, aber Schreibmethode eins und Schreiben Methode zwei ist gleichwertig, sie sind der Inhalt, wenn die gesamte Methode gesperrt ist.

在用synchronized修饰方法时要注意以下几点: 
1. synchronized关键字不能继承。 
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下: 
在子类方法中加上synchronized关键字

class Parent {
   public synchronized void method() { }
}class Child extends Parent {
   public synchronized void method() { }
}

在子类方法中调用父类的同步方法

class Parent {
   public synchronized void method() {   }
}class Child extends Parent {
   public void method() { super.method();   }
}

在定义接口方法时不能使用synchronized关键字。

构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。 

修饰一个静态的方法

Synchronized也可修饰一个静态方法,用法如下:

public synchronized static void method() {
   // todo
}

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对Demo1进行一些修改如下:

【Demo5】:synchronized修饰静态方法

/**
 * 同步线程
 */class SyncThread implements Runnable {   private static int count;   public SyncThread() {
      count = 0;
   }   public synchronized static void method() {      for (int i = 0; i < 5; i ++) {         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }   public synchronized void run() {
      method();
   }
}

调用代码:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

结果如下:

SyncThread1:0 
SyncThread1:1 
SyncThread1:2 
SyncThread1:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread2:7 
SyncThread2:8 
SyncThread2:9

syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这与Demo1是不同的。

修饰一个类

Synchronized还可作用于一个类,用法如下:

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

我们把Demo5再作一些修改。 
【Demo6】:修饰一个类

/**
 * 同步线程
 */
class SyncThread implements Runnable {
   private static int count;

   public SyncThread() {
      count = 0;
   }

   public static void method() {
      synchronized(SyncThread.class) {
         for (int i = 0; i < 5; i ++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }

   public synchronized void run() {
      method();
   }
}

其效果和【Demo5】是一样的,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。

总结:

A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn