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; } }
4. Verwenden Sie Wiedereintrittssperren, um eine Thread-Synchronisierung zu erreichen.
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.
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(); } } }
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.
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() : 移除并返回队头元素,如果队列空则阻塞
代码实例:
实现商家生产商品和买卖商品的同步
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 }
注:BlockingQueue
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞
7.使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
代码实例:
只改Bank类,其余代码与上面第一个例子同
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 }
补充--原子操作主要有:
对于引用变量和大多数原始变量(long和double除外)的读写操作;
对于所有使用volatile修饰的变量(包括long和double)的读写操作。
相关文章:
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!