In diesem Artikel werden hauptsächlich relevante Informationen zu verschiedenen Methoden zur Synchronisierung von Java-Multithreads vorgestellt. Hier sind 5 Methoden, auf die sich Freunde in Not beziehen können:
Mehrere Methoden zur Synchronisierung von Java-Multithreads
1. Einleitung
Vor ein paar Tagen wurde ich vom Meister gefoltert wieder Grundkenntnisse. Kommen wir ohne weitere Umschweife zum Punkt.
2. Warum Thread-Synchronisierung erforderlich ist
Denn wenn wir mehrere Threads haben, die gleichzeitig auf eine Variable oder ein Objekt zugreifen, wenn es in diesen Leser gibt Threads Wenn ein weiterer Schreibvorgang ausgeführt wird, werden der Variablenwert oder der Status des Objekts verwirrt, was zu einer Programmausnahme führt. Wenn beispielsweise ein Bankkonto von zwei Threads gleichzeitig betrieben wird, hebt einer 100 Yuan ab und der andere zahlt 100 Yuan ein. Angenommen, das Konto hat ursprünglich 0 Blöcke, wenn der Auszahlungsthread und der Einzahlungsthread gleichzeitig stattfinden. Die Geldabhebung ist fehlgeschlagen und der Kontostand beträgt 100. Die Geldabhebung ist erfolgreich und der Kontostand beträgt 0. Welches ist es also? Es ist schwer zu sagen. Daher soll die Multithread-Synchronisation dieses Problem lösen.
3. Code bei Nichtsynchronisierung
Bank.java
package threadTest; /** * @author ww * */ public class Bank { private int count =0;//账户余额 //存钱 public void addMoney(int money){ count +=money; System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public void subMoney(int money){ if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
SyncThreadTest .java
package threadTest; /** * Java学习交流QQ群:589809992 我们一起学Java! */ public class SyncThreadTest { public static void main(String args[]){ final Bank bank=new Bank(); Thread tadd=new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } bank.addMoney(100); bank.lookMoney(); System.out.println("\n"); } } }); Thread tsub = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ bank.subMoney(100); bank.lookMoney(); System.out.println("\n"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); tsub.start(); tadd.start(); } }
Der Code ist sehr einfach, daher werde ich ihn nicht erklären. Mal sehen, wie die Ergebnisse aussehen. Ich habe einen Teil davon herausgeschnitten. Es ist sehr chaotisch und ich kann es nicht verstehen.
余额不足 账户余额:0 余额不足 账户余额:100 1441790503354存进:100 账户余额:100 1441790504354存进:100 账户余额:100 1441790504354取出:100 账户余额:100 1441790505355存进:100 账户余额:100 1441790505355取出:100 账户余额:100
4. Code bei Verwendung der Synchronisierung
(1) Synchronisierungsmethode:
Es gibt eine Methode zum Ändern des synchronisierten Schlüsselworts. Da jedes Objekt in Java über eine integrierte Sperre verfügt, schützt die integrierte Sperre die gesamte Methode, wenn eine Methode mit diesem Schlüsselwort geändert wird. Bevor Sie diese Methode aufrufen, müssen Sie die integrierte Sperre erhalten, andernfalls wird sie blockiert.
Modified Bank.java
package threadTest; /** * @author ww * */ public class Bank { private int count =0;//账户余额 //存钱 public synchronized void addMoney(int money){ count +=money; System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public synchronized void subMoney(int money){ if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
Sehen Sie sich die laufenden Ergebnisse an:
余额不足 账户余额:0 余额不足 账户余额:0 1441790837380存进:100 账户余额:100 1441790838380取出:100 账户余额:0 1441790838380存进:100 账户余额:100 1441790839381取出:100 账户余额:0
I Ich habe das Gefühl, dass ich es sofort verstehen kann.
Hinweis: Das synchronisierte Schlüsselwort kann auch statische Methoden ändern. Wenn die statische Methode zu diesem Zeitpunkt aufgerufen wird, wird die gesamte Klasse gesperrt
(2) Synchronisierter Codeblock
Das heißt, der durch das synchronisierte Schlüsselwort geänderte Anweisungsblock. Der durch dieses Schlüsselwort geänderte Kontoauszugsblock wird automatisch mit einer integrierten Sperre hinzugefügt, um eine Synchronisierung zu erreichen.
Der Bank.java-Code lautet wie folgt:
package threadTest; /** * @author ww * */ public class Bank { private int count =0;//账户余额 //存钱 public void addMoney(int money){ synchronized (this) { count +=money; } System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public void subMoney(int money){ synchronized (this) { if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; } System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
Die Laufergebnisse sind wie folgt:
余额不足 账户余额:0 1441791806699存进:100 账户余额:100 1441791806700取出:100 账户余额:0 1441791807699存进:100 账户余额:100
Der Effekt ist ähnlich wie bei Methode eins.
Hinweis: Die Synchronisierung ist ein Vorgang mit hohem Overhead, daher sollte der synchronisierte Inhalt minimiert werden. Normalerweise ist es nicht erforderlich, die gesamte Methode zu synchronisieren. Verwenden Sie einfach synchronisierte Codeblöcke, um den Schlüsselcode zu synchronisieren.
(3) Verwenden Sie spezielle Domänenvariablen (Volatile), um eine Thread-Synchronisierung zu erreichen.
a Das Schlüsselwort volatile bietet einen sperrfreien Mechanismus für den Zugriff auf Domänenvariablen. b ist äquivalent zu Teilen Sie der virtuellen Maschine mit, dass dieses Feld von anderen Threads aktualisiert werden kann. Daher muss das Feld jedes Mal neu berechnet werden, anstatt den Wert im Register zu verwenden. Volatile bietet keine atomaren Operationen und kann dies auch nicht Es wird verwendet, um endgültige Typen zu ändern. Der
Bank.java-Code lautet wie folgt:
package threadTest; /** * @author ww * */ public class Bank { private volatile int count = 0;// 账户余额 // 存钱 public void addMoney(int money) { count += money; System.out.println(System.currentTimeMillis() + "存进:" + money); } // 取钱 public void subMoney(int money) { if (count - money < 0) { System.out.println("余额不足"); return; } count -= money; System.out.println(+System.currentTimeMillis() + "取出:" + money); } // 查询 public void lookMoney() { System.out.println("账户余额:" + count); } }
Was ist der laufende Effekt? ?
余额不足 账户余额:0 余额不足 账户余额:100 1441792010959存进:100 账户余额:100 1441792011960取出:100 账户余额:0 1441792011961存进:100 账户余额:100
Sind Sie schon wieder verwirrt? Warum ist das so? Dies liegt daran, dass Volatile keine atomaren Operationen garantieren kann und daher synchronisierte Operationen nicht durch Volatilität ersetzen kann. Darüber hinaus verhindert Volatilität, dass der Compiler den Code optimiert. Wenn Sie ihn also nicht verwenden können, wenden Sie ihn nicht an. Sein Prinzip besteht darin, dass ein Thread jedes Mal, wenn er auf eine flüchtig geänderte Variable zugreifen möchte, diese aus dem Speicher liest, anstatt sie aus dem Cache zu lesen, sodass der Variablenwert, auf den jeder Thread zugreift, derselbe ist. Dadurch wird die Synchronisation gewährleistet.
(4) Verwenden Sie Wiedereintrittssperren, um eine Thread-Synchronisierung zu erreichen
Ein neues java.util.concurrent-Paket wurde in JavaSE5.0 hinzugefügt, um die Synchronisierung zu unterstützen. Die ReentrantLock-Klasse ist eine wiedereintrittsfähige, sich gegenseitig ausschließende Sperre, die die Lock-Schnittstelle implementiert. Sie weist das gleiche grundlegende Verhalten und die gleiche Semantik wie die Verwendung synchronisierter Methoden und Blöcke auf und erweitert ihre Funktionen. Häufig verwendete Methoden der ReentrantLock-Klasse sind: ReentrantLock(): Erstellen einer ReentrantLock-Instanz lock(): Abrufen der Sperre unlock(): Freigeben der Sperre Hinweis: ReentrantLock() verfügt auch über einen Konstruktor, der eine faire Sperre erstellen kann, aber weil er kann das Programm erheblich reduzieren. Aus betrieblichen Gründen wird nicht empfohlen, die Bank.java-Codeänderung wie folgt zu verwenden:
package threadTest; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author ww * */ public class Bank { private int count = 0;// 账户余额 //需要声明这个锁 private Lock lock = new ReentrantLock(); // 存钱 public void addMoney(int money) { lock.lock();//上锁 try{ count += money; System.out.println(System.currentTimeMillis() + "存进:" + money); }finally{ lock.unlock();//解锁 } } // 取钱 public void subMoney(int money) { lock.lock(); try{ if (count - money < 0) { System.out.println("余额不足"); return; } count -= money; System.out.println(+System.currentTimeMillis() + "取出:" + money); }finally{ lock.unlock(); } } // 查询 public void lookMoney() { System.out.println("账户余额:" + count); } }
Was ist der laufende Effekt?
余额不足 账户余额:0 余额不足 账户余额:0 1441792891934存进:100 账户余额:100 1441792892935存进:100 账户余额:200 1441792892954取出:100 账户余额:100
Der Effekt ist ähnlich wie bei den ersten beiden Methoden.
Wenn das synchronisierte Schlüsselwort die Anforderungen der Benutzer erfüllen kann, verwenden Sie synchronisiert, da es den Code vereinfachen kann. Wenn Sie erweiterte Funktionen benötigen, verwenden Sie die ReentrantLock-Klasse. Achten Sie zu diesem Zeitpunkt darauf, die Sperre rechtzeitig aufzuheben, da sonst ein Deadlock auftritt. Die Sperre wird normalerweise im endgültigen Code freigegeben
(5) Verwendung Lokale Variablen zum Erreichen der Thread-Synchronisation
Bank.java-Code lautet wie folgt:
package threadTest; /** * @author ww * */ public class Bank { private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { // TODO Auto-generated method stub return 0; } }; // 存钱 public void addMoney(int money) { count.set(count.get()+money); System.out.println(System.currentTimeMillis() + "存进:" + money); } // 取钱 public void subMoney(int money) { if (count.get() - money < 0) { System.out.println("余额不足"); return; } count.set(count.get()- money); System.out.println(+System.currentTimeMillis() + "取出:" + money); } // 查询 public void lookMoney() { System.out.println("账户余额:" + count.get()); } }
Ausführungseffekt:
余额不足 账户余额:0 余额不足 账户余额:0 1441794247939存进:100 账户余额:100 余额不足 1441794248940存进:100 账户余额:0 账户余额:200 余额不足 账户余额:0 1441794249941存进:100 账户余额:300
Sehen Sie, ich war zunächst verwirrt über den Operationseffekt. Warum ist nur das Speichern, aber nicht das Abheben erlaubt? Schauen Sie sich das Prinzip von ThreadLocal an:
Wenn Sie ThreadLocal zum Verwalten von Variablen verwenden, erhält jeder Thread, der die Variable verwendet, eine Kopie der Variablen. Die Kopien sind unabhängig voneinander, sodass jeder Thread seine eigene Kopie der Variablen nach Belieben ändern kann, ohne dass es zu Problemen kommt Einfluss auf andere Threads. Jetzt verstehen Sie, dass jeder Thread eine Kopie ausführt, was bedeutet, dass das Einzahlen und Abheben von Geld zwei Konten mit demselben Wissensnamen sind. Der obige Effekt wird also auftreten.
ThreadLocal- und Synchronisationsmechanismus
a. ThreadLocal und der Synchronisationsmechanismus dienen beide dazu, das Problem von Zugriffskonflikten derselben Variablen in Multithreads zu lösen. b „Raum gegen Zeit“-Methode, letztere übernimmt die Methode des „Austauschs von Zeit gegen Raum“
Das obige ist der detaillierte Inhalt vonEinführung in verschiedene Methoden der Java-Multithread-Synchronisierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!