Maison  >  Article  >  Java  >  Quels sont les mécanismes des différents verrous en Java

Quels sont les mécanismes des différents verrous en Java

王林
王林avant
2023-04-20 08:34:121057parcourir

Avant-propos

Résumé des verrous courants en Java

Distinguer chaque mécanisme de verrouillage et comment l'utiliser

Méthode d'utilisation Nom du verrou
Inspecter si le thread souhaite verrouiller les ressources synchronisées Optimiste verrouillage et verrouillage pessimiste
Après avoir verrouillé la ressource de synchronisation, souhaitez-vous bloquer ? Si vous ne bloquez pas, vous pouvez utiliser un verrou tournant
Un thread et plusieurs processus acquièrent le même verrou Verrouillage réentrant
Plusieurs threads partagent un seul verrou Verrou en lecture-écriture (verrou partagé pour l'écriture)
Si plusieurs threads sont en concurrence pour faire la queue Verrouillage équitable et verrouillage injuste

1. Verrouillage optimiste et verrouillage pessimiste

Verrouillage pessimiste : plusieurs personnes ne peuvent pas être exécutées en même temps. Verrouillez d'abord lors de l'exécution. De nombreux mécanismes de verrouillage de ce type sont utilisés dans les bases de données relationnelles traditionnelles, tels que les verrous de ligne, les verrous de table, les verrous de lecture, les verrous d'écriture, etc., qui sont tous verrouillés avant les opérations

Verrouillage optimiste : que le numéro de version soit cohérent ou non, c'est-à-dire , en ajoutant une version aux données, en mettant à jour les données de manière synchrone et en ajoutant un numéro de version. Il ne sera pas verrouillé, le numéro de version peut être déterminé et peut être utilisé par plusieurs personnes, comme pour la saisie de tickets dans la vie. Chaque fois que vous allez chercher les données, vous pensez que d'autres ne les modifieront pas, elles ne seront donc pas verrouillées. Cependant, lors de la mise à jour, vous jugerez si d'autres ont mis à jour les données pendant cette période. Vous pouvez utiliser des mécanismes tels que la version. Nombres. Le verrouillage optimiste convient aux types d'applications à lectures multiples, ce qui peut améliorer le débit. Redis utilise ce mécanisme de vérification et de définition pour implémenter les transactions

(le verrouillage optimiste peut être implémenté à l'aide du mécanisme de numéro de version et de l'algorithme CAS)

Quels sont les mécanismes des différents verrous en Java

Démontrer le verrouillage pessimiste et optimiste à travers des cas spécifiques

dans le framework redis

Avant d'exécuter multi, exécutez la commande watch

Le format spécifique est le suivant

watch key1 [key2]

Le format de code spécifique est le suivant

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set add 100
OK
127.0.0.1:6379> watch add
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby add 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 120
127.0.0.1:6379>

flushdb consiste à effacer la base de données

Quels sont les mécanismes des différents verrous en Java

Mais si vous entrez exec sur un autre serveur, une erreur s'affichera

Parce que l'utilisation est un verrou optimiste, et la version changera après avoir été modifiée

En général :

Verrouillage pessimiste : lorsque tout le monde termine les choses seul, le verrouillage et le déverrouillage sont exécutés. Pour résoudre le problème de la concurrence, il ne prend pas en charge les opérations simultanées et ne peut fonctionner qu'une par une, ce qui est inefficace.

Verrouillage optimiste : chaque fois que quelque chose est exécuté, le numéro de version des données sera comparé, celui qui soumet en premier soumettra la version le numéro en premier

2. Équité Serrures et serrures injustes

Verrouillage équitable : premier arrivé, premier servi

Verrouillage injuste : pas dans l'ordre, la file d'attente peut être sautée

  • Verrouillage équitable : efficacité relativement faible

  • Verrouillage injuste : haute efficacité, mais facile à enfiler Mourir de faim

grâce à cette fonction Lock lock = new ReentrantLock(true);. Créez un verrou réentrant, vrai signifie un verrouillage équitable, faux signifie un verrouillage injuste. Le verrou injuste par défaut

En visualisant le code source

ReentrantLock (true) avec les paramètres est un verrou équitable

ReentrantLock(false) est un verrou injuste

Appelant principalement NonfairSync() et FairSync()

public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

Plus précisément, il n'est pas Fair Lock et le code source de Fair Lock

Voir le code source de Fair Lock

static final class FairSync extends Sync {
   private static final long serialVersionUID = -3000897897090466540L;

  /**
  * Acquires only if reentrant or queue is empty.
   */
  final boolean initialTryLock() {
   Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
   if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
     setExclusiveOwnerThread(current);
      return true;
    }
    } else if (getExclusiveOwnerThread() == current) {
      if (++c < 0) // overflow
          throw new Error("Maximum lock count exceeded");
         setState(c);
         return true;
       }
    return false;
}

Opération détaillée à travers des exemples de code

//第一步  创建资源类,定义属性和和操作方法
class LTicket {
    //票数量
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock(true);
    //卖票方法
    public void sale() {
        //上锁
        lock.lock();
        try {
            //判断是否有票
            if(number > 0) {
                System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);
            }
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

public class LSaleTicket {
    //第二步 创建多个线程,调用资源类的操作方法
    //创建三个线程
    public static void main(String[] args) {

        LTicket ticket = new LTicket();

new Thread(()-> {
    for (int i = 0; i < 40; i++) {
        ticket.sale();
    }
},"AA").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"BB").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"CC").start();
    }
}

La capture d'écran du résultat est la suivante

Quels sont les mécanismes des différents verrous en Java

Tous sont exécutés par le thread A, mais le thread BC n'est pas exécuté et une erreur non standard se produit Fair lock

Des modifications spécifiques à ses paramètres peuvent être apportées via un constructeur paramétré dans le verrou réentrant

Modifier le code en privé final ReentrantLock lock = new ReentrantLock(true );

La capture d'écran du code est

Quels sont les mécanismes des différents verrous en Java

3. Verrouillage réentrant disponible

Le verrouillage réentrant est également appelé verrouillage récursif

Et avec le verrouillage réentrant, après avoir craqué le premier, vous pouvez toujours entrer dans la structure interne

Object o = new Object();
new Thread(()->{
    synchronized(o) {
        System.out.println(Thread.currentThread().getName()+" 外层");

        synchronized (o) {
            System.out.println(Thread.currentThread().getName()+" 中层");

            synchronized (o) {
                System.out.println(Thread.currentThread().getName()+" 内层");
            }
        }
    }

},"t1").start();

synchronisé (o) signifie verrouiller le { } courant Le bloc de code à l'intérieur

Ce qui précède sont tous des mécanismes de verrouillage synchronisés

Le mécanisme de verrouillage du verrou est expliqué ci-dessous

public class SyncLockDemo {

    public synchronized void add() {
        add();
    }

    public static void main(String[] args) {
        //Lock演示可重入锁
        Lock lock = new ReentrantLock();
        //创建线程
        new Thread(()->{
            try {
                //上锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" 外层");

                try {
                    //上锁
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+" 内层");
                }finally {
                    //释放锁
                    lock.unlock();
                }
            }finally {
                //释放做
                lock.unlock();
            }
        },"t1").start();

        //创建新线程
        new Thread(()->{
            lock.lock();
            System.out.println("aaaa");
            lock.unlock();
        },"aa").start();
        }
 }

Dans le verrou imbriqué dans la même serrure, le verrou imbriqué interne peut sera toujours affiché s'il n'est pas déverrouillé, mais si vous sautez hors du fil, exécutez un autre Un fil provoquera une impasse

Pour comprendre le concept de verrouillage et de déverrouillage, vous devez écrire

Quels sont les mécanismes des différents verrous en Java

4. verrou (verrou partagé et verrou exclusif)

Le verrou en lecture est un verrou partagé, le verrou en écriture est un verrou exclusif

  • Une implémentation spécifique du verrou partagé

  • le verrou en lecture-écriture gère un ensemble de verrous, l'un est un verrou en lecture -verrouillage uniquement et l'autre est un verrou en écriture.

Verrouillage en lecture-écriture : une ressource est accessible par plusieurs threads de lecture ou par un seul thread d'écriture, mais les threads de lecture et d'écriture ne peuvent pas exister en même temps, exclusion mutuelle en lecture-écriture, partage en lecture-lecture (verrouillage en écriture exclusif , verrouillage en lecture partagé), la priorité du verrouillage en écriture est supérieure au verrouillage en lecture)

Le verrouillage en lecture-écriture ReentrantReadWriteLock

Le verrouillage en lecture est ReentrantReadWriteLock.ReadLock, méthode readLock()

Le verrouillage en écriture est ReentrantReadWriteLock.WriteLock, méthode writeLock()

Créer un objet de verrouillage en lecture-écriture privé ReadWriteLock rwLock = new ReentrantReadWriteLock();

Verrou en écriture rwLock.writeLock().lock();, déverrouiller rwLock.writeLock().unlock();

Verrou en lecture rwLock.readLock() .lock ();, le déverrouillage est rwLock.readLock().unlock();

Analyse de cas :

Simulez le multi-threading pour récupérer et lire les données dans la carte

Le code complet est le suivant

//资源类
class MyCache {
    //创建map集合
    private volatile Map<String,Object> map = new HashMap<>();

    //创建读写锁对象
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    //放数据
    public void put(String key,Object value) {
        //添加写锁
        rwLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
            //暂停一会
            TimeUnit.MICROSECONDS.sleep(300);
            //放数据
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+" 写完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放写锁
            rwLock.writeLock().unlock();
        }
    }

    //取数据
    public Object get(String key) {
        //添加读锁
        rwLock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
            //暂停一会
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 取完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放读锁
            rwLock.readLock().unlock();
        }
        return result;
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        //创建线程放数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        TimeUnit.MICROSECONDS.sleep(300);

        //创建线程取数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

5. Le verrouillage Mutex

le verrouillage d'exclusion mutuelle est une implémentation conventionnelle du verrouillage exclusif. Cela signifie qu'une certaine ressource ne permet qu'à un seul visiteur d'y accéder en même temps. Elle est unique et exclusive

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//创建互斥锁并初始化

pthread_mutex_lock(&mutex);//对线程上锁,此时其他线程阻塞等待该线程释放锁

//要执行的代码段

pthread_mutex_unlock(&mutex);//执行完后释放锁

6. explication. Les détails sont les suivants :

它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名

通俗的来说就是一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务。

其特点:

  1. 持有锁时间等待过长,消耗CPU

  2. 无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题

  3. 自旋锁不会使线程状态发生切换,处于用户态(不会到内核态进行线程的状态转换),一直都是活跃,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。

其模拟算法如下

do{
	b=1;
	while(b){
		lock(bus);
		b = test_and_set(&lock);
		unlock(bus);
	}
	//临界区
	//lock = 0;
	//其余部分
}while(1)

7. 无锁 / 偏向锁 / 轻量级锁 / 重量级锁

  • 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功

  • 偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价

  • 轻量级锁:锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能

  • 重量级锁:线程并发加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,还有线程要访问

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer