Maison  >  Article  >  Java  >  Verrouiller le partage du résumé d'utilisation

Verrouiller le partage du résumé d'utilisation

巴扎黑
巴扎黑original
2017-06-23 10:50:452101parcourir

Verrouillage

Dans l'article précédent, nous avons expliqué comment utiliser le mot-clé synchronisé pour obtenir un accès synchrone. Dans cet article, nous continuons à discuter de ce problème. À partir de Java 5, un autre moyen d'obtenir un accès synchrone est fourni dans le package java.util.concurrent.locks, à savoir Lock.

Certains amis peuvent demander, puisque l'accès synchronisé peut être obtenu via la synchronisation, pourquoi devons-nous fournir un verrouillage ? Cette question sera abordée ci-dessous. Cet article commence par les lacunes de synchronisé, puis décrit les classes et interfaces couramment utilisées sous le package java.util.concurrent.locks, et aborde enfin les éléments suivants sur le concept de verrous

Défauts synchronisés

Nous avons dit plus tôt qu'il existe deux situations dans lesquelles un thread synchronisé libère le verrou :

  1. Le bloc de code ou la méthode synchronisée est exécuté

  2. Lorsqu'une exception se produit dans un bloc de code ou une méthode de synchronisation, la jvm libère automatiquement le verrou

Vous pouvez voir sur le verrou de libération synchronisé au-dessus de Out, le verrou ne sera libéré qu'après l'exécution du bloc de code synchronisé ou si une exception se produit. Si le programme dans le bloc de code est bloqué pour des raisons d'E/S, le thread ne sera jamais libéré. le verrou, mais à ce moment-là, d'autres threads exécuteront toujours d'autres programmes, ce qui affecte grandement l'efficacité d'exécution du programme. Nous avons maintenant besoin d'un mécanisme qui puisse empêcher le thread d'attendre indéfiniment et de répondre aux interruptions. lock

De plus, s'il existe un programme qui contient plusieurs threads de lecture et un thread d'écriture, nous pouvons savoir que synchronisé ne peut être exécuté que par un seul thread, mais nous avons besoin de plusieurs threads de lecture. lire en même temps, donc utiliser synchronisé n'est certainement pas possible, mais nous utilisons le verrouillage de la même manière. Cela peut être fait

Verrouillage

En regardant le. API, nous pouvons voir que Lock est une interface, donc les objets ne peuvent pas être créés directement, mais nous pouvons utiliser les classes qu'elle implémente pour créer un objet, ne nous inquiétez pas pour cela, jetons d'abord un coup d'œil aux méthodes implémentées par Lock. class Nous expliquerons l'implémentation spécifique en détail lors de la présentation des classes qu'elle implémente

méthodes

  • lock() Obtenez le verrou, sinon. il attendra éternellement

  • unlock() Libérez le verrou

  • tryLock() Essayez d'acquérir le verrou si le verrou est acquis avec succès, exécutez-le. Si le verrou n'est pas acquis avec succès, alors il n'y aura pas d'attente

  • lockInterruptibly() Si le thread actuel n'est pas interrompu, le verrou est acquis.

ReentrantLock

ReentrantLock est un verrou réentrant C'est une classe qui implémente l'interface Lock est un mécanisme d'allocation de threads, réentrant. Cela signifie qu'il est toujours alloué au thread qui a récemment obtenu le verrou. Il s'agit d'un mécanisme d'allocation injuste, et bien sûr, afin de résoudre ce phénomène, la méthode de construction de ReentrantLock fournit également un paramètre équitable . juste est vrai, cela signifie qu'en utilisant le mécanisme d'allocation équitable, le thread avec le temps d'attente le plus long obtiendra le verrou

Méthode de construction

  • ReentrantLock() Créer un objet et utiliser le mécanisme réentrant par défaut

  • ReentrantLock(boolean fair) Si fair est vrai, alors le mécanisme d'allocation équitable est utilisé

Méthodes courantes

  • lock() Acquérir le verrou S'il n'est pas acquis, il bloquera toujours

.

Ce qui suit utilise un programme pour démontrer l'utilisation de la méthode de verrouillage suivante. Le code est le suivant :

//实现接口的线程类public class MyThread implements Runnable {public ReentrantLock rLock = null;  //注意这里的锁一定要是全局变量,否则每一个线程都创建一把锁,那么将会毫无意义
 public MyThread() {this.rLock = new ReentrantLock(); // 创建默认的可重入锁}// 将unlock方法放在finally中确保执行中代码出现异常仍然能够释放锁,否则将会造成其它的线程阻塞public void display() {this.rLock.lock(); // 获取锁try {for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "正在输出"+ i);
            }
        } finally {this.rLock.unlock(); // 释放锁,注意这步是一定需要的}

    }@Overridepublic void run() {this.display(); // 调用display方法}

}//线程的测试类,主要是创建对象启动线程public class Test {public static void main(String[] args) {final MyThread thread = new MyThread(); // 创建对象// 下面创建两个线程,并且直接启动,new Thread(thread).start();new Thread(thread).start();

    }
}

Exécutez le. ci-dessus pour obtenir le résultat comme indiqué ci-dessous :

Verrouiller le partage du résumé dutilisation

À partir des résultats ci-dessus, nous pouvons voir que les threads génèrent un par un, et le suivant ne peut être exécuté qu'après avoir attendu la fin de la sortie d'un thread. Un thread, ici est uniquement pour le code entre le verrouillage et le déverrouillage, l'autre code n'est pas contrôlé

Remarque : La création ici peut être répétée. L'objet verrouillé doit être une variable globale pour chaque thread et un objet pouvant être partagé. Si vous créez cet objet dans la méthode d'affichage, cela n'a aucun sens car. chaque thread n'utilise pas du tout le même. Lock

  • boolean tryLock() Essayez d'abord d'acquérir le verrou. Si le verrou est acquis, exécutez-le, sinon. cela n'attendra pas éternellement

Utilisez un morceau de code pour essayer la méthode suivante :

<.>
//实现接口的线程类public class MyThread implements Runnable {public ReentrantLock rLock = null; // 注意这里的锁一定要是全局变量,否则每一个线程都创建一把锁,那么将会毫无意义public MyThread() {this.rLock = new ReentrantLock(); // 创建默认的可重入锁}// 将unlock方法放在finally中确保执行中代码出现异常仍然能够释放锁,否则将会造成其它的线程阻塞public void display() {if (this.rLock.tryLock()) // 如果获取了锁{try {for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()
                            + "正在输出" + i);
                }
            } finally {this.rLock.unlock(); // 释放锁,注意这步是一定需要的}

        } else {
            System.out.println(Thread.currentThread().getName()
                    + "获取锁失败,我将不会一直等待........");
        }

    }@Overridepublic void run() {this.display(); // 调用display方法}

}//线程的测试类,主要是创建对象启动线程public class Test {public static void main(String[] args) {final MyThread thread = new MyThread(); // 创建对象// 下面创建两个线程,并且直接启动,new Thread(thread).start();new Thread(thread).start();

    }
}

执行后的Verrouiller le partage du résumé dutilisation如下图:

Verrouiller le partage du résumé dutilisation

从上面的Verrouiller le partage du résumé dutilisation我们知道线程0获取了锁开始执行,但是线程1并没有获取锁,但是使用的是tryLock并不是lock,因此不会一直等待下去,所以直接程序向下运行,直接跳过上锁的代码段,因此就输出了上面的那句话后直接结

ReadWriteLock

从API中可以知道,这个也是一个接口,用于实现读写线程,他有两个方法:Lock readLock(),Lock writeLock() 分别用于获得读锁和写锁,指定特定的锁可以实现特定的功能,比如读锁可以在写线程在执行的情况下可以实现多个读线程进行操作,下面我们来介绍它的具体的实现的类ReentrantReadWriteLock

ReentrantReadWriteLock

这个类也是一个可重入分配的类,当然前面已经说过了什么是可重入,现在我们来说说说这个类的详细的用法

构造方法

  • ReentrantReadWriteLock() 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock。

  • ReentrantReadWriteLock(boolean fair) 使用给定的公平策略创建一个新的ReentrantReadWriteLock。

常用的方法

  • ReentrantReadWriteLock.ReadLock readLock() 用于返回读取操作的锁

前面已经说过读取操作的锁是用来实现多个线程共同执行的,代码如下:

//实现接口的线程类public class MyThread implements Runnable {public ReentrantReadWriteLock rwlock = null;public Lock rLock = null;public MyThread() {this.rwlock = new ReentrantReadWriteLock(); // 创建对象,使用的是非公平的this.rLock = this.rwlock.readLock(); // 获取读取锁对象}// 将unlock方法放在finally中确保执行中代码出现异常仍然能够释放锁,否则将会造成其它的线程阻塞public void display() {this.rLock.lock(); // 获取读取锁try {for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "正在输出"+ i);
            }
        } finally {this.rLock.unlock(); // 释放锁,注意这步是一定需要的}

    }@Overridepublic void run() {this.display(); // 调用display方法}

}//线程的测试类,主要是创建对象启动线程public class Test {public static void main(String[] args) {final MyThread thread = new MyThread(); // 创建对象// 下面创建两个线程,并且直接启动,for(int i=0;i<5;i++)
        {new Thread(thread).start();
        }
        


    }
}

执行上面的程序Verrouiller le partage du résumé dutilisation如下:

Verrouiller le partage du résumé dutilisation

从上面的Verrouiller le partage du résumé dutilisation可以知道,其实使用读取操作是多个线程同时进行读取的操作,因此一定要小心谨慎的使用,根据自己的需求,一般不能在里面进行修改了,因为出现Verrouiller le partage du résumé dutilisation不准确的Verrouiller le partage du résumé dutilisation,这个就不多说了,相信大家都明白,总之要小心使用

  • ReentrantReadWriteLock.WriteLock writeLock() 返回用于写入操作的锁

写入操作的锁和读取操作的锁不一样了,因为一次只能允许一个线程执行写入操作。

并且如果一个线程已经占用了读锁,另外一个线程申请写锁将会一直等待线程释放读锁。

如果一个线程已经占用了写锁,另外一个线程申请读锁,那么这个线程将会一直等待线程释放写锁才能执行。

总之意思就是写线程和读线程不能同时执行,但是多个读线程可以同时执行

下面将使用一个程序详细的体会以下读写锁的综合使用,代码如下:

//实现接口的线程类public class MyThread {public ReentrantReadWriteLock rwlock = null;public Lock rLock = null;public Lock wLock = null;public ArrayList<Integer> arrayList = null;public MyThread() {this.rwlock = new ReentrantReadWriteLock(); // 创建对象,使用的是非公平的this.rLock = this.rwlock.readLock(); // 获取读取锁对象arrayList = new ArrayList<>(); // 实例化this.wLock = this.rwlock.writeLock(); // 获取写入锁对象}// 将unlock方法放在finally中确保执行中代码出现异常仍然能够释放锁,否则将会造成其它的线程阻塞// //向arraylist中写入数据public void put() {this.wLock.lock(); // 获取写入锁try {for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()
                        + "正在执行写入操作,写入" + i);this.arrayList.add(i);
            }
        } finally {this.wLock.unlock();
        }

    }// 从arraylist中读取数据,这里只是随机读取使用的是get,并没有做什么修改,因为这仅仅是读取操作,如果进行了修改必须实现同步public void get() {this.rLock.lock(); // 获取读取操作的锁Random random = new Random();if (!arrayList.isEmpty()) {try {for (int i = 0; i < 10; i++) {int index = random.nextInt(this.arrayList.size() - 1);int data = this.arrayList.get(index);
                    System.out.println(Thread.currentThread().getName()
                            + "正在读取数据     " + data);
                }
            } finally {this.rLock.unlock();

            }
        } else {
            System.out.println("ArrayList为空");
        }

    }

}//线程的测试类,主要是创建对象启动线程public class Test {public static void main(String[] args) {final MyThread thread = new MyThread(); // 创建对象ArrayList<Thread> arrayList = new ArrayList<>();/*         * 创建8个读线程,2个写线程         */for (int i = 0; i < 2; i++) {
            arrayList.add(new Thread() {@Overridepublic void run() {
                    thread.put();
                }
            });

        }        for(int i=0;i<8;i++)
        {
            arrayList.add(new Thread(){@Overridepublic void run() {
                    thread.get();
                }
            });
        }        
        
        
        for (Thread t : arrayList) {
            t.start();
        }

    }
}

Verrouiller le partage du résumé dutilisation如下图:

Verrouiller le partage du résumé dutilisation

从上面可以看出写入线程都是一个一个执行的,读取线程是一起执行的

注意: 所有的锁对象对于线程来说必须是全局变量,否则毫无意义。读线程只能进行不影响线程安全性的操作,比如不能进行对数据的修改插入,如果想要进行修改的话必须还要使用锁对必要的代码实现同步操作

参考文章


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:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn