Das synchronisierte Schlüsselwort stellt die Sperre dieser Methode dar. Dies bedeutet, dass unabhängig davon, welcher Thread A jedes Mal diese Methode ausführt, überprüft werden muss, ob andere Threads B (oder C, D usw.) diese Methode verwenden. Wenn ja, müssen Sie warten, bis Thread B (oder C D), der diese Methode verwendet, die Ausführung dieser Methode abgeschlossen hat, bevor Sie diesen Thread A ausführen. Wenn nicht, umfasst die direkte Ausführung zwei Verwendungen: synchronisierte Methode und synchronisierter Block.
1. synchronisierte Methode:
Deklarieren Sie die synchronisierte Methode, indem Sie das synchronisierte Schlüsselwort zur Methodendeklaration hinzufügen. Beispiel:
public synchronized void accessVal(int newVal);
Die synchronisierte Methode steuert den Zugriff auf Klassenmitgliedsvariablen: Jede Klasseninstanz entspricht einer Sperre, und jede synchronisierte Methode muss die Sperre der Klasseninstanz erhalten, die die Methode aufruft, bevor sie aktiviert werden kann andernfalls wird der Thread, zu dem die Methode gehört, blockiert und die Sperre wird erst nach der Rückkehr von der Methode freigegeben. Danach kann der blockierte Thread die Sperre erhalten - Geben Sie den ausführbaren Status ein. Dieser Mechanismus stellt sicher, dass sich für jede Klasseninstanz gleichzeitig höchstens eine ihrer als synchronisiert deklarierten Mitgliedsfunktionen im ausführbaren Zustand befindet (da höchstens die der Klasseninstanz entsprechende Sperre erhalten werden kann), wodurch die Notwendigkeit effektiv vermieden wird für Klassenmitglieder zu Zugriffsverletzungen auf Variablen (solange alle Methoden, die auf Variablen von Klassenmitgliedern zugreifen können, als synchronisiert deklariert sind). In Java entsprechen nicht nur Klasseninstanzen, sondern auch jede Klasse einer Sperre. Auf diese Weise können wir auch die statischen Mitgliedsfunktionen der Klasse als synchronisiert deklarieren, um ihren Zugriff auf die statischen Mitgliedsvariablen der Klasse zu steuern. Mängel der synchronisierten Methode: Wenn eine große Methode als synchronisiert deklariert wird, wird die Effizienz stark beeinträchtigt. Wenn die Thread-Klassenmethode run() als synchronisiert deklariert wird, wird sie normalerweise während des gesamten Lebenszyklus des Threads weiter ausgeführt . Führt dazu, dass die Aufrufe aller synchronisierten Methoden dieser Klasse niemals erfolgreich sind. Natürlich können wir dieses Problem lösen, indem wir den Code, der auf Klassenmitgliedsvariablen zugreift, in eine spezielle Methode einfügen, ihn als synchronisiert deklarieren und ihn in der Hauptmethode aufrufen, aber Java bietet uns eine bessere Lösung, nämlich den synchronisierten Block.
2. synchronisierter Block:
Deklarieren Sie den synchronisierten Block über das synchronisierte Schlüsselwort. Die Syntax lautet wie folgt:
synchronized(syncObject) { //允许访问控制的代码 }
Ein synchronisierter Block ist ein Codeblock, in dem der Code die Sperre des Objekts syncObject (wie bereits erwähnt, kann es eine Klasseninstanz oder eine Klasse sein) davor erhalten muss Der spezifische Mechanismus ist derselbe wie zuvor beschrieben. Da es auf jeden Codeblock abzielen und das gesperrte Objekt beliebig angeben kann, bietet es eine hohe Flexibilität.
Einiges Verständnis von synchronisiert (dies)
1. Wenn zwei gleichzeitige Threads auf den synchronisierten (diesen) Synchronisationscodeblock im selben Objekt zugreifen, kann ihn jeweils nur ein Thread implementieren. Ein anderer Thread muss warten, bis der aktuelle Thread die Ausführung dieses Codeblocks abgeschlossen hat, bevor er diesen Codeblock ausführen kann.
2. Wenn ein Thread auf einen synchronisierten (diesen) synchronisierten Codeblock des Objekts zugreift, wird der Zugriff anderer Threads auf alle anderen synchronisierten (diesen) synchronisierten Codeblöcke im Objekt blockiert.
3. Wenn jedoch ein Thread auf einen synchronisierten (diesen) synchronisierten Codeblock eines Objekts zugreift, kann ein anderer Thread immer noch auf andere Teile des Objekts als den synchronisierten (diesen) synchronisierten Codeblock zugreifen.
4. Das dritte Beispiel ist auch auf andere synchronisierte Codeblöcke anwendbar. Das heißt, wenn ein Thread auf einen synchronisierten (diesen) synchronisierten Codeblock eines Objekts zugreift, erhält er die Objektsperre dieses Objekts. Dadurch wird der Zugriff anderer Threads auf alle synchronisierten Codeteile des Objektobjekts vorübergehend blockiert.
5. Die oben genannten Regeln gelten auch für andere Objektsperren.
Ein einfaches Beispiel für synchronisiert
public class TextThread { /** * @param args */ public static void main(String[] args) { // TODO 自动生成方法存根 TxtThread tt = new TxtThread(); new Thread(tt).start(); new Thread(tt).start(); new Thread(tt).start(); new Thread(tt).start(); } } class TxtThread implements Runnable { int num = 100; String str = new String(); public void run() { while (true) { synchronized(str) { if (num>0) { try { Thread.sleep(10); } catch(Exception e) { e.getMessage(); } System.out.println(Thread.currentThread().getName()+ "this is "+ num--); } } } } }
Um einen Zeitunterschied zu erzeugen, der die Möglichkeit für Fehler bietet, wird Thread.sleep(10) von Java verwendet Unterstützung für Multithreading und Der Synchronisierungsmechanismus wird von allen geliebt. Es scheint, dass die Verwendung des synchronisierten Schlüsselworts das Problem der Multithread-Synchronisierung gemeinsamer Daten leicht lösen kann. Wie? ——Bevor wir eine Schlussfolgerung ziehen können, müssen wir die Rolle des synchronisierten Schlüsselworts genau verstehen.
Im Allgemeinen kann das synchronisierte Schlüsselwort als Modifikator einer Funktion oder als Anweisung innerhalb einer Funktion verwendet werden, die normalerweise als Synchronisationsmethode und Synchronisationsanweisungsblock bezeichnet wird. Bei weiterer Klassifizierung kann synchronisiert auf Instanzvariablen, Objektreferenzen (Objektreferenzen), statische Funktionen und Klassenliterale (Klassennamenliterale) einwirken. Bevor wir weiter darauf eingehen, müssen wir einige Punkte klären:
A. Unabhängig davon, ob das synchronisierte Schlüsselwort einer Methode oder einem Objekt hinzugefügt wird, handelt es sich bei der Sperre, die es erhält, um ein Objekt, anstatt einen Codeabschnitt oder eine Funktion als Sperre zu behandeln – und es ist wahrscheinlich, dass Objekte von anderen auf die synchronisierte Methode zugreifen Threads.
B. Jedem Objekt ist nur eine Sperre zugeordnet.
C. Das Erreichen einer Synchronisierung erfordert einen hohen Systemaufwand und kann sogar zu einem Deadlock führen. Vermeiden Sie daher unnötige Synchronisierungskontrollen.
Als nächstes diskutieren wir die Auswirkungen der Synchronisierung an verschiedenen Stellen auf den Code: Angenommen, P1 und P2 sind unterschiedliche Objekte derselben Klasse. Diese Klasse definiert Synchronisierungsblöcke oder Synchronisierungsmethoden in den folgenden Situationen: P1, P2 kann sie alle anrufen.
1. Bei Verwendung von synchronisiert als Funktionsmodifikator lautet der Beispielcode wie folgt:
Public synchronized void methodAAA() { //… }
这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:
public void methodAAA() { synchronized (this) // (1) { //….. } }
(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。
2.同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so) { //….. } }
这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable { private byte[] lock = new byte[0]; // 特殊的instance变量 Public void methodA() { synchronized(lock) { //… } } //….. }
注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo { public synchronized static void methodAAA() // 同步的static 函数 { //…. } public void methodBBB() { synchronized(Foo.class) // class literal(类名称字面常量) } }
代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
小结如下:
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。 还有一些技巧可以让我们对共享资源的同步访问更加安全:1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了 还有,比较常用的就有:Collections.synchronizedMap(new HashMap()),当然这个MAP就是生命在类中的全局变量,就是一个线程安全的HashMap,web的application是全web容器公用的,所以要使用线程安全来保证数据的正确。