In the previous article we talked about how to use the keyword synchronized to achieve synchronous access. In this article, we continue to discuss this issue. Starting from Java 5, another way to achieve synchronous access is provided under the java.util.concurrent.locks package, and that is Lock.
Maybe some friends will ask, since synchronized access can be achieved through synchronized, why do we need to provide Lock? This issue will be addressed below. This article starts with the shortcomings of synchronized, then goes on to describe the commonly used classes and interfaces under the java.util.concurrent.locks package, and finally discusses the following things about the concept of locks
We said earlier that there are two situations when a synchronized thread releases the lock:
The code block or the synchronized method is executed
When an exception occurs in a code block or synchronization method, the jvm automatically releases the lock
You can see from the synchronized release lock above Out, the lock will only be released after the execution of the synchronized code block is completed or an exception occurs. If the program in the code block is blocked due to IO reasons, the thread will never release the lock, but at this time other threads will still execute other programs, which is greatly It affects the execution efficiency of the program. Now we need a mechanism to prevent the thread from waiting indefinitely and to respond to interrupts. This can be done through lock
In addition, if There is a program that contains multiple reading threads and one writing thread. We can know that synchronized can only be executed by one thread, but we need multiple reading threads to read at the same time, so using synchronized is definitely not possible, but we use lock in the same way. It can be done
Looking at the API, we can see that Lock is an interface, so objects cannot be created directly, but we can use the classes it implements to create Object, don’t worry about this. Let’s first take a look at what methods are implemented by the Lock class. We will explain the specific implementation in detail when introducing the class it implements
lock()
Acquire the lock. If it is not obtained, it will wait forever.
unlock()
Release the lock
tryLock()
Try to obtain the lock. If the lock is obtained successfully, execute it. If the lock is not obtained successfully, then there will be no waiting.
lockInterruptibly()
Acquire the lock if the current thread has not been interrupted.
ReentrantLock is a reentrant lock. It is a class that implements the Lock interface. ReentrantLock is a thread allocation mechanism. , reentrant means that it is always allocated to the thread that recently obtained the lock. This is an unfair allocation mechanism, and starvation will occur. Of course, in order to solve this phenomenon, the construction method of ReentrantLock also provides a fair parameter. , if fair is true, it means using the fair allocation mechanism, the thread with the longest waiting time will obtain the lock
ReentrantLock()
Create an object, the reentrant mechanism is used by default
ReentrantLock(boolean fair)
Used if fair is true It is a fair allocation mechanism
##lock()
Get the lock, if not Obtaining will always block
The following is a program to demonstrate the use of the following lock method. The code is as follows:
//实现接口的线程类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(); } }
Execute the above code to get the result below:
##It can be seen from the above result , threads are output one by one, and the next thread can only be executed after waiting for the output of one thread to be completed. The code here is only for the code between lock and unlock, and other codes are not controlled.
Note:The reentrant lock object created here must be a global variable for each thread and an object that can be shared. If you create this object in the display method , then it is meaningless, because each thread does not use the same lock at all
Let’s use a piece of code to try the following method. The code is as follows:
- boolean tryLock()
First try to obtain Lock, if the lock is obtained, execute it, otherwise it will not wait forever
//实现接口的线程类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(); } }
执行后的Lock usage summary sharing如下图:
从上面的Lock usage summary sharing我们知道线程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(); } } }
执行上面的程序Lock usage summary sharing如下:
从上面的Lock usage summary sharing可以知道,其实使用读取操作是多个线程同时进行读取的操作,因此一定要小心谨慎的使用,根据自己的需求,一般不能在里面进行修改了,因为出现Lock usage summary sharing不准确的Lock usage summary sharing,这个就不多说了,相信大家都明白,总之要小心使用
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(); } } }
Lock usage summary sharing如下图:
从上面可以看出写入线程都是一个一个执行的,读取线程是一起执行的
注意: 所有的锁对象对于线程来说必须是全局变量,否则毫无意义。读线程只能进行不影响线程安全性的操作,比如不能进行对数据的修改插入,如果想要进行修改的话必须还要使用锁对必要的代码实现同步操作
The above is the detailed content of Lock usage summary sharing. For more information, please follow other related articles on the PHP Chinese website!