Heim  >  Artikel  >  Java  >  【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten)

【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten)

php是最好的语言
php是最好的语言Original
2018-08-06 11:58:184291Durchsuche

Warum die Synchronisierung verwenden?

Java ermöglicht die gleichzeitige Steuerung mehrerer Threads (z. B. Hinzufügen, Löschen, Ändern und Überprüfen von Daten),
führt Um ungenaue Daten und Konflikte miteinander zu verursachen, wird eine Synchronisationssperre hinzugefügt, um zu verhindern, dass sie von anderen Threads aufgerufen wird, bevor der Thread den Vorgang abschließt.
Dadurch wird die Einzigartigkeit und Genauigkeit der Variablen sichergestellt.

1. Synchronisationsmethode
Es gibt eine Methode, die durch das synchronisierte Schlüsselwort geändert wird.
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 synchronisiert void save(){}

Hinweis: Das synchronisierte Schlüsselwort kann zu diesem Zeitpunkt auch die statische Methode ändern, wenn die statische Methode aufgerufen wird. Die gesamte Klasse

2. Der synchronisierte Codeblock
ist ein Anweisungsblock, der mit dem synchronisierten Schlüsselwort geändert wird.
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 Schlüsselcodes zu synchronisieren.
Codebeispiel:

package com.xhj.thread;

    /**
     * 线程同步的运用
     * 
     * @author XIEHEJUN
     * 
     */
    public class SynchronizedThread {

        class Bank {

            private int account = 100;

            public int getAccount() {
                return account;
            }

            /**
             * 用同步方法实现
             * 
             * @param money
             */
            public synchronized void save(int money) {
                account += money;
            }

            /**
             * 用同步代码块实现
             * 
             * @param money
             */
            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }
        }

        class NewThread implements Runnable {
            private Bank bank;

            public NewThread(Bank bank) {
                this.bank = bank;
            }

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "账户余额为:" + bank.getAccount());
                }
            }

        }

        /**
         * 建立线程,调用内部类
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("线程1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("线程2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }

        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }

    }

3. Verwenden Sie spezielle Domänenvariablen (flüchtig), um eine Thread-Synchronisierung zu erreichen.

Das Schlüsselwort a.volatile ist Domäne Der Zugriff auf Variablen bietet einen sperrfreien Mechanismus,
b. Die Verwendung von volatile zum Ändern einer Domäne ist gleichbedeutend damit, der virtuellen Maschine mitzuteilen, dass die Domäne jedes Mal aktualisiert werden kann Wenn es verwendet wird, muss es neu berechnet werden, anstatt den Wert im Register
zu verwenden. d.volatile bietet keine atomaren Operationen und kann auch nicht zum Ändern endgültiger Typvariablen verwendet werden.
Zum Beispiel:
Oben Fügen Sie beispielsweise einfach vor dem Konto eine flüchtige Änderung hinzu, um eine Thread-Synchronisierung zu erreichen.
Codebeispiel:

      //只给出要修改的代码,其余代码与上同
        class Bank {
            //需要同步的变量加上volatile
            private volatile int account = 100;

            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }

Hinweis: Das Nichtsynchronisationsproblem beim Multithreading tritt hauptsächlich beim Lesen und Schreiben in die Domäne auf. Wenn die Domäne selbst dieses Problem vermeidet, besteht keine Notwendigkeit Ändern Sie die Domänenmethoden.

Verwenden Sie endgültige Felder, sperrgeschützte Felder und flüchtige Felder, um asynchrone Probleme zu vermeiden.


4. Verwenden Sie Wiedereintrittssperren, um eine Thread-Synchronisierung zu erreichen.

In JavaSE5.0 wurde ein neues Paket java.util.concurrent zur Unterstützung der Synchronisierung hinzugefügt.

Die ReentrantLock-Klasse ist eine wiedereintrittsfähige, sich gegenseitig ausschließende Sperre, die die Lock-Schnittstelle implementiert.
Sie hat das gleiche grundlegende Verhalten und die gleiche Semantik wie die Verwendung synchronisierter Methoden und Blöcke und erweitert ihre Fähigkeiten.

ReenreantLock Wird häufig verwendet Methoden der Klasse sind:

ReentrantLock(): Erstellen Sie eine ReentrantLock-Instanz

lock(): Erhalten Sie die Sperre
unlock(): Geben Sie die Sperre frei
Hinweis: Es gibt eine weitere Option für ReentrantLock() Erstellen Sie eine Konstruktionsmethode für faire Sperren. Da dies jedoch die Effizienz der Programmausführung erheblich verringern kann, wird die Verwendung von
nicht empfohlen. Beispiel:
Basierend auf dem obigen Beispiel lautet der umgeschriebene Code:
Codebeispiel:

//只给出要修改的代码,其余代码与上同
        class Bank {
            
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }

Hinweis: Bezüglich der Auswahl des Sperrobjekts und des synchronisierten Schlüsselworts:


a. Es ist am besten, keines von beiden zu verwenden und einen von bereitgestellten Mechanismus zu verwenden das java.util.concurrent-Paket

Es kann Benutzern helfen, mit allen sperrbezogenen Codes umzugehen.
b. Wenn das synchronisierte Schlüsselwort die Anforderungen der Benutzer erfüllen kann, verwenden Sie synchronisiert, da es den Code vereinfachen kann.
Es kommt zu einem Deadlock. Normalerweise wird die Sperre im endgültigen Code aufgehoben


5. Verwenden Sie lokale Variablen, um eine Thread-Synchronisierung zu erreichen Wenn Sie ThreadLocal zum Verwalten von Variablen verwenden, erhält jeder Thread, der die Variable verwendet, Die Kopien dieser Variablen
sind unabhängig voneinander, sodass jeder Thread dies tun kann Ändern Sie Ihre eigene Kopie der Variablen nach Belieben, ohne andere Threads zu beeinträchtigen.

Gemeinsame Methoden der ThreadLocal-Klasse

    ThreadLocal() : 创建一个线程本地变量 
    get() : 返回此线程局部变量的当前线程副本中的值 
    initialValue() : 返回此线程局部变量的当前线程的"初始值" 
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

    例如: 
        在上面例子基础上,修改后的代码为:     
    代码实例: 

//只改Bank类,其余代码与上同
        public class Bank{
            //使用ThreadLocal类管理共享变量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }

    注:ThreadLocal与同步机制 
        a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
        b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方法

6.使用阻塞队列实现线程同步

    前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 
    使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 
    本小节主要是使用LinkedBlockingQueue来实现线程的同步 
    LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。 
    队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~ 
    
   LinkedBlockingQueue 类常用方法 
    LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
    size() : 返回队列中的元素个数 
    take() : 移除并返回队头元素,如果队列空则阻塞 
    
   代码实例: 
        实现商家生产商品和买卖商品的同步

【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten)

 1 package com.xhj.thread;
 2 
 3 import java.util.Random;
 4 import java.util.concurrent.LinkedBlockingQueue;
 5 
 6 /**
 7  * 用阻塞队列实现线程同步 LinkedBlockingQueue的使用
 8  * 
 9  * @author XIEHEJUN
10  * 
11  */
12 public class BlockingSynchronizedThread {
13     /**
14      * 定义一个阻塞队列用来存储生产出来的商品
15      */
16     private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
17     /**
18      * 定义生产商品个数
19      */
20     private static final int size = 10;
21     /**
22      * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
23      */
24     private int flag = 0;
25 
26     private class LinkBlockThread implements Runnable {
27         @Override
28         public void run() {
29             int new_flag = flag++;
30             System.out.println("启动线程 " + new_flag);
31             if (new_flag == 0) {
32                 for (int i = 0; i < size; i++) {
33                     int b = new Random().nextInt(255);
34                     System.out.println("生产商品:" + b + "号");
35                     try {
36                         queue.put(b);
37                     } catch (InterruptedException e) {
38                         // TODO Auto-generated catch block
39                         e.printStackTrace();
40                     }
41                     System.out.println("仓库中还有商品:" + queue.size() + "个");
42                     try {
43                         Thread.sleep(100);
44                     } catch (InterruptedException e) {
45                         // TODO Auto-generated catch block
46                         e.printStackTrace();
47                     }
48                 }
49             } else {
50                 for (int i = 0; i < size / 2; i++) {
51                     try {
52                         int n = queue.take();
53                         System.out.println("消费者买去了" + n + "号商品");
54                     } catch (InterruptedException e) {
55                         // TODO Auto-generated catch block
56                         e.printStackTrace();
57                     }
58                     System.out.println("仓库中还有商品:" + queue.size() + "个");
59                     try {
60                         Thread.sleep(100);
61                     } catch (Exception e) {
62                         // TODO: handle exception
63                     }
64                 }
65             }
66         }
67     }
68 
69     public static void main(String[] args) {
70         BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
71         LinkBlockThread lbt = bst.new LinkBlockThread();
72         Thread thread1 = new Thread(lbt);
73         Thread thread2 = new Thread(lbt);
74         thread1.start();
75         thread2.start();
76 
77     }
78 
79 }

【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten)

注:BlockingQueue定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

  add()方法会抛出异常

  offer()方法返回false

  put()方法会阻塞

7.使用原子变量实现线程同步

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
代码实例:
只改Bank类,其余代码与上面第一个例子同

【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten)

 1 class Bank {
 2         private AtomicInteger account = new AtomicInteger(100);
 3 
 4         public AtomicInteger getAccount() {
 5             return account;
 6         }
 7 
 8         public void save(int money) {
 9             account.addAndGet(money);
10         }
11     }

【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten)

补充--原子操作主要有:
  对于引用变量和大多数原始变量(long和double除外)的读写操作;
  对于所有使用volatile修饰的变量(包括long和double)的读写操作。

 相关文章:

关于Java线程同步和同步方法的详解

详解Java多线程编程中的线程同步方法

Das obige ist der detaillierte Inhalt von【Java】Warum Synchronisierung verwenden? Informationen zur Thread-Synchronisierung (7 Möglichkeiten). 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