Heim  >  Artikel  >  Java  >  So synchronisieren Sie Java

So synchronisieren Sie Java

爱喝马黛茶的安东尼
爱喝马黛茶的安东尼Original
2019-11-12 17:59:203258Durchsuche

So synchronisieren Sie Java

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-Synchronisierung

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.

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!

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
Vorheriger Artikel:So serialisieren Sie in JavaNächster Artikel:So serialisieren Sie in Java