Maison >Java >javaDidacticiel >[java] Pourquoi utiliser la synchronisation ? À propos de la synchronisation des threads (7 façons)
Pourquoi utiliser la synchronisation ?
Java permet le contrôle de la concurrence multithread. Lorsque plusieurs threads exploitent une variable de ressource partageable en même temps (comme l'ajout, la suppression, la modification et la vérification de données),
mènera. aux données inexactes, en conflit les unes avec les autres, un verrou de synchronisation est donc ajouté pour éviter d'être appelé par d'autres threads avant que le thread ne termine l'opération,
garantissant ainsi l'unicité et l'exactitude de la variable.
1. Méthode de synchronisation
Il existe une méthode modifiée par le mot-clé synchronisé.
Puisque chaque objet en Java possède un verrou intégré, lorsqu'une méthode est modifiée avec ce mot-clé,
le verrou intégré protégera l'ensemble de la méthode. Avant d'appeler cette méthode, vous devez obtenir le verrou intégré, sinon il sera bloqué.
Code tel que :
public synchronisé void save(){}
Remarque : Le mot-clé synchronisé peut également modifier la méthode statique À ce stade, si la méthode statique est appelée, la classe entière
2. Le bloc de code synchronisé
est un bloc d'instructions modifié avec le mot-clé synchronisé.
Le bloc d'instructions modifié par ce mot-clé sera automatiquement ajouté avec un verrou intégré pour réaliser la synchronisation
Code tel que :
synchronized(object){ }
Remarque : la synchronisation est un opérations de surcharge de haut niveau, le contenu de la synchronisation doit donc être minimisé.
Habituellement, il n'est pas nécessaire de synchroniser l'ensemble de la méthode, utilisez simplement des blocs de codes synchronisés pour synchroniser les codes clés.
Exemple de code :
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. Utilisez des variables de domaine spéciales (volatiles) pour réaliser la synchronisation des threads
a.volatile mot-clé Fournit un mécanisme sans verrouillage pour l'accès aux variables de domaine,
b. Utiliser volatile pour modifier un domaine équivaut à indiquer à la machine virtuelle que le domaine peut être mis à jour par d'autres threads,
c. domaine est utilisé, il doit être actualisé Calculer au lieu d'utiliser la valeur dans le registre
d.volatile ne fournit aucune opération atomique, et ne peut pas non plus être utilisé pour modifier les variables de type final
Par exemple :
Dans Dans l'exemple ci-dessus, utilisez simplement le compte Ajouter une modification volatile devant pour réaliser la synchronisation des threads.
Exemple de code :
//只给出要修改的代码,其余代码与上同 class Bank { //需要同步的变量加上volatile private volatile int account = 100; public int getAccount() { return account; } //这里不再需要synchronized public void save(int money) { account += money; } }
Remarque : Le problème de non-synchronisation en multi-threading se produit principalement dans les champs de lecture et d'écriture. Si le champ lui-même évite ce problème, aucune modification n'est nécessaire. Méthodes pour opérer sur ce domaine.
Utilisez les champs finaux, les champs protégés par verrouillage et les champs volatiles pour éviter les problèmes asynchrones.
4. Utilisez des verrous de réentrance pour réaliser la synchronisation des threads
Un nouveau package java.util.concurrent a été ajouté dans JavaSE5.0 pour prendre en charge la synchronisation.
La classe ReentrantLock est un verrou réentrant et mutuellement exclusif qui implémente l'interface Lock
Elle a le même comportement de base et la même sémantique que l'utilisation de méthodes et de blocs synchronisés, et étend ses capacités
ReenreantLock Couramment utilisé. les méthodes de la classe sont :
ReentrantLock() : Créer une instance ReentrantLock
lock() : Obtenir le verrou
unlock() : Libérer le verrou
Remarque : Il existe une autre option pour ReentrantLock() Crée une méthode de construction de verrou équitable, mais comme elle peut réduire considérablement l'efficacité de l'exécution du programme, il n'est pas recommandé d'utiliser
Par exemple :
Sur la base de l'exemple ci-dessus, le code réécrit est :
Code exemple :
//只给出要修改的代码,其余代码与上同 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(); } } }
Remarque : Concernant la sélection de l'objet Lock et du mot-clé synchronisé :
a. Il est préférable de ne pas utiliser l'un ou l'autre, et d'en utiliser un fourni par. le mécanisme du package java.util.concurrent,
peut aider les utilisateurs à gérer tous les codes liés au verrouillage.
b. Si le mot-clé synchronisé peut répondre aux besoins des utilisateurs, utilisez synchronisé, car cela peut simplifier le code
Un blocage se produira, généralement le verrou est libéré dans le code final
5. Utilisez des variables locales pour réaliser la synchronisation des threads
Si vous utilisez ThreadLocal pour gérer les variables, chaque thread utilisant la variable obtiendra Les copies de cette variable,
sont indépendantes les unes des autres, afin que chaque thread puisse modifier sa propre copie de la variable à volonté sans affecter les autres threads.
Méthodes courantes de la classe ThreadLocal
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)的读写操作。
相关文章:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!