前回の記事では、キーワード synchronized を使用して同期アクセスを実現する方法について説明しました。この記事では、引き続きこの問題について説明します。Java 5 以降では、同期アクセスを実現する別の方法が java.util.concurrent.locks パッケージで提供されています。それが Lock です。
同期アクセスは同期によって実現できるのに、なぜロックを提供する必要があるのかと尋ねる友人もいるかもしれません。この問題については以下で説明します。この記事は、synchronized の欠陥から始まり、次に java.util.concurrent.locks パッケージで一般的に使用されるクラスとインターフェースについて説明し、最後にロックの概念について次のことについて説明します
前に述べたこと 同期スレッドがロックを解放する場合は 2 つの状況があると言われています:
コード ブロックまたは同期メソッドが実行される
コード ブロックまたは同期メソッドと jvm で例外が発生する自動的にロックを解放します
上記から、同期されたコードブロックが実行されるか、コードブロック内のプログラムがブロックされた場合にのみ例外が発生した場合にのみロックが解放されることがわかります。 IO の理由により、スレッドがロックを解放することはありませんが、この時点でも他のプログラムが実行されるため、スレッドが無限に待機することを防ぎ、応答するメカニズムが必要になります。これはロックを通じて実行できます
さらに、プログラムには複数の読み取りスレッドと 1 つの書き込みスレッドが含まれている場合、同期は 1 つのスレッドでのみ実行できることがわかりますが、複数の読み取りスレッドが必要です。同時に読み取る必要があるため、同期を使用することは間違いなく不可能ですが、ロックを使用することもできます。
に移動して API を確認すると、Lock はインターフェイスであるため、オブジェクトを作成できないことがわかります。直接使用できますが、実装されているクラスを使用してオブジェクトを作成することもできます。最初に Lock クラスを見てみましょう。具体的な実装については、クラスを紹介するときに詳しく説明します。 implements
lock()
ロックを取得します。取得できない場合は待ち続けますlock()
获取锁,如果没有获得就会一直等待
unlock()
释放锁
tryLock()
尝试获得锁,如果成功获得锁就执行,如果没有成功获得锁,那么就不会等待了
lockInterruptibly()
如果当前线程未被中断,则获取锁。
ReentrantLock是可重入锁,是实现Lock接口的一个类,可重入是一种线程的分配机制,可重入的意思就是总是分配给最近获得锁的线程,这是一种不公平的分配机制,将会出现饥饿现象,当然为了解决这种现象,ReentrantLock的构造方法还提供了一个fair参数,如果fair为true表示使用公平分配机制,将会有等待时间最长的线程获得锁
ReentrantLock()
创建一个对象,默认使用的时可重入的机制
ReentrantLock(boolean fair)
如果fair为true那么使用的是公平分配机制
lock()
获取锁,如果没有获取到将会一直阻塞
下面使用一段程序演示以下lock方法的使用,代码如下:
//实现接口的线程类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(); } }
执行上面的代码得到下图的ロック使用状況の概要の共有:
从上面的ロック使用状況の概要の共有看出,线程是一个一个输出的,并且只有等待一个线程输出完毕才能执行下一个线程,这里的仅仅是针对lock和unlock之间的代码,之外的代码并不是受到控制
注意: 这里的创建的可重入锁的对象必须对于每一个线程来说是全局的变量,是可以共享的一个对象,如果你在display方法中创建这个对象,那么是毫无意义的,因为每一个线程用的根本不是同一把锁
boolean tryLock()
unlock()
を解放しますlock
tryLock()
ロックの取得を試みます。ロックの取得が成功した場合は実行し、ロックの取得が成功しなかった場合は、待ち時間はありません
🎜< code>lockInterruptibly() 現在のスレッドが中断されていない場合は、ロックを取得します。 🎜🎜🎜🎜ReentrantLock🎜🎜🎜 ReentrantLock は、Lock インターフェースを実装するクラスです。 Reentrant は、常にロックを取得したスレッドに割り当てられることを意味します。もちろん、この現象を解決するために、ReentrantLock のコンストラクタは、fair パラメータも提供します。fair が true の場合、それは公平な割り当てメカニズムが使用されていることを意味します。待ち時間。最長のスレッドがロックを取得します🎜🎜🎜構築メソッド🎜🎜🎜🎜🎜ReentrantLock()
デフォルトでリエントラントメカニズムを使用してオブジェクトを作成します🎜🎜🎜🎜ReentrantLock(boolean) Fair )
Fair が true の場合、公平な割り当てメカニズムが使用されます🎜🎜🎜🎜🎜共通メソッド🎜🎜🎜🎜🎜lock()
ロックが取得されていない場合は、ロックを取得します。常にブロックされます🎜 🎜🎜🎜🎜🎜 以下では、プログラムを使用して次のロック メソッドの使用を示します。コードは次のとおりです。🎜🎜🎜//实现接口的线程类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(); } }
boolean tryLock()
ロックが取得された場合は、まずロックを取得しようとします。そうでない場合は、待機しません。永遠に🎜🎜🎜🎜🎜🎜 コードの一部を使用して次のメソッドを試してみましょう。コードは次のとおりです:🎜🎜🎜//实现接口的线程类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(); } } }
执行后的ロック使用状況の概要の共有如下图:
从上面的ロック使用状況の概要の共有我们知道线程0获取了锁开始执行,但是线程1并没有获取锁,但是使用的是tryLock并不是lock,因此不会一直等待下去,所以直接程序向下运行,直接跳过上锁的代码段,因此就输出了上面的那句话后直接结
从API中可以知道,这个也是一个接口,用于实现读写线程,他有两个方法:Lock readLock(),Lock writeLock() 分别用于获得读锁和写锁,指定特定的锁可以实现特定的功能,比如读锁可以在写线程在执行的情况下可以实现多个读线程进行操作,下面我们来介绍它的具体的实现的类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(); } } }
执行上面的程序ロック使用状況の概要の共有如下:
从上面的ロック使用状況の概要の共有可以知道,其实使用读取操作是多个线程同时进行读取的操作,因此一定要小心谨慎的使用,根据自己的需求,一般不能在里面进行修改了,因为出现ロック使用状況の概要の共有不准确的ロック使用状況の概要の共有,这个就不多说了,相信大家都明白,总之要小心使用
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(); } } }
ロック使用状況の概要の共有如下图:
从上面可以看出写入线程都是一个一个执行的,读取线程是一起执行的
注意: 所有的锁对象对于线程来说必须是全局变量,否则毫无意义。读线程只能进行不影响线程安全性的操作,比如不能进行对数据的修改插入,如果想要进行修改的话必须还要使用锁对必要的代码实现同步操作
以上がロック使用状況の概要の共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。