Warum Synchronisierung erforderlich ist
Java ermöglicht die Steuerung der Multithread-Parallelität, wenn mehrere Threads gemeinsam genutzte Ressourcenvariablen betreiben (z. B. das Hinzufügen, Löschen, Ändern und Abfragen von Daten) führt zu ungenauen Daten und zu Konflikten untereinander. Daher wird eine Synchronisationssperre hinzugefügt, um zu verhindern, dass der Thread von anderen Threads aufgerufen wird, um den Vorgang abzuschließen, wodurch sichergestellt wird, dass der Thread Eindeutigkeit und Genauigkeit von Variablen.
1. Beispiel
Wenn ein Bankkonto beispielsweise von zwei Threads gleichzeitig betrieben wird, nimmt einer 100 Yuan 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 Auszahlung war nicht erfolgreich und der Kontostand beträgt 100. Die Auszahlung war erfolgreich und der Kontostand beträgt 0. Doch welcher Saldo entspricht welchem? Es ist schwer, es klar zu sagen, daher entsteht das Problem der Multithread-Synchronisation.
2. Situation ohne Synchronisierung
Wenn beispielsweise ein Bankkonto von zwei Threads gleichzeitig betrieben wird, nimmt einer 100 Yuan und der andere zahlt 100 Yuan ein . . Angenommen, das Konto hat ursprünglich 0 Blöcke, wenn der Auszahlungsthread und der Einzahlungsthread gleichzeitig stattfinden. Bei erfolgloser Geldabhebung beträgt der Kontostand 100. Bei erfolgreicher Geldabhebung beträgt der Kontostand 0. Doch welcher Saldo entspricht welchem? Es ist schwer, es klar zu sagen, daher entsteht das Problem der Multithread-Synchronisation.
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); } }
package threadTest; 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(); } }
Laufende Ergebnisse:
1502542307917取出:100 账号余额:100 1502542308917存进:100 1502542308917取出:100 账号余额:0 账号余额:0 1502542309917存进:100 账号余额:0 1502542309917取出:100 账号余额:0
Zu diesem Zeitpunkt tritt ein Nicht-Thread-Sicherheitsproblem auf, da zwei Threads gleichzeitig auf eine nicht synchronisierte Methode zugreifen, wenn diese beiden Threads Instanzen im Geschäftsobjekt betreiben Gleichzeitig kann es zu Sicherheitsproblemen außerhalb des Threads kommen.
Lösung: Fügen Sie einfach das synchronisierte Schlüsselwort vor public void run() hinzu.
3. Synchronisierungsmethode
synchronisierte Schlüsselwortänderungsmethode
Es gibt eine synchronisierte Schlüsselwortänderungsmethode. 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.
Code wie:
public synchronized void save(){}
Hinweis: Das synchronisierte Schlüsselwort kann auch statische Methoden ändern. Wenn zu diesem Zeitpunkt die statische Methode aufgerufen wird, wird die gesamte Klasse gesperrt.
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); } }
Laufergebnis:
余额不足 账号余额:0 1502543814934存进:100 账号余额:100 1502543815934存进:100 账号余额:200 1502543815934取出:100 账号余额:100
Thread-Synchronisation wird auf diese Weise erreicht
Synchronisierter Codeblock
wird mit der Synchronisierung geändert Schlüsselwortblock von Anweisungen.
Der durch dieses Schlüsselwort geänderte Anweisungsblock wird automatisch mit einer integrierten Sperre hinzugefügt, um eine Synchronisierung zu erreichen.
Code wie:
synchronized(object){ }
Hinweis: Die Synchronisierung ist ein High- Kostenmethode Betrieb, daher sollten synchronisierte Inhalte minimiert werden.
Normalerweise ist es nicht erforderlich, die gesamte Methode zu synchronisieren. Verwenden Sie einfach synchronisierte Codeblöcke, um den Schlüsselcode zu synchronisieren.
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 laufenden Ergebnisse sind wie folgt:
余额不足 账户余额:0 余额不足 账户余额:100 1502544966411存进:100 账户余额:100 1502544967411存进:100 账户余额:100 1502544967411取出:100 账户余额:100 1502544968422取出:100
Dadurch wird auch eine Thread-Synchronisierung erreicht, und die Betriebseffizienz ist höher als bei der Methodensynchronisierung. Da die Synchronisierung ein kostenintensiver Vorgang ist, sollte sie minimiert werden . Inhalt. Normalerweise ist es nicht erforderlich, die gesamte Methode zu synchronisieren. Verwenden Sie einfach synchronisierte Codeblöcke, um den Schlüsselcode zu synchronisieren.
Verwenden Sie spezielle Domänenvariablen (flüchtig), um eine Thread-Synchronisierung zu erreichen
ein.volatile-Schlüsselwort bietet einen sperrfreien Mechanismus für den Zugriff auf Mitgliedsvariablen;
b. Die Verwendung von „volatil“ zum Ändern von Mitgliedsvariablen ist gleichbedeutend damit, dass das Feld von anderen Threads aktualisiert werden kann.
c. Daher muss es jedes Mal neu berechnet werden Verwenden des Werts im Register. Value; d.volatile bietet keine atomaren Operationen und kann auch nicht zum Ändern endgültiger Typvariablen verwendet werden.
Bank.java-Code lautet wie folgt:
package com.thread.demo; /** * Created by HJS on 2017/8/12. */ public class Bank { private volatile 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); } }
Laufendes Ergebnis:
余额不足 账户余额:0 余额不足 账户余额:100 1502546287474存进:100 账户余额:100 1502546288474存进:100 1502546288474取出:100 账户余额:100
Zu diesem Zeitpunkt ist die Bestellung erneut durcheinander, was darauf hinweist, dass ein weiteres Synchronisierungsproblem vorliegt. weil volatile keine atomaren Operationen garantieren kann Aus diesem Grund kann volatile synchronisierte Operationen nicht ersetzen. 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.
Verwenden von Wiedereintrittssperren zur Erzielung der Thread-SynchronisierungEin 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.
Gemeinsame Methoden der ReenreantLock-Klasse sind:
ReentrantLock(): Erstellen Sie eine ReentrantLock-Instanz
lock(): Erhalten Sie die Sperre
unlock() : Sperre freigeben
Hinweis: ReentrantLock() verfügt auch über eine Konstruktionsmethode, mit der eine faire Sperre erstellt werden kann. Dies wird jedoch nicht empfohlen, da dies die Ausführungseffizienz des Programms erheblich beeinträchtigen kann.
Bank.java-Code wird wie folgt geändert:
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); } }
Laufergebnisse:
余额不足 账户余额:0 1502547439892存进:100 账户余额:100 1502547440892存进:100 账户余额:200 1502547440892取出:100 账户余额:100
Hinweis: Bezüglich der Auswahl des Sperrobjekts und des synchronisierten Schlüsselworts:
a. Es ist am besten, keines davon zu verwenden und einen Mechanismus zu verwenden, der vom Paket java.util.concurrent bereitgestellt wird, um Benutzern bei der Handhabung des gesamten sperrbezogenen Codes zu helfen.
b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码。
c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。
使用局部变量实现线程同步
代码如下:
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()); } }
运行结果如下:
复制代码 余额不足 账户余额:0 余额不足 1502547748383存进:100 账户余额:100 账户余额:0 余额不足 账户余额:0 1502547749383存进:100 账户余额:200
看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。
ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
注:ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式。
php中文网,大量的免费Java入门教程,欢迎在线学习!
Das obige ist der detaillierte Inhalt vonSo synchronisieren Sie Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!