一、可重入锁与不可重入锁的理解

Java多线程有阻塞函数wait()方法和通知函数notify()方法

wait():阻塞当前线程

notify():唤起被wait()阻塞的线程

这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出IllegalMonitorStateException异常,所以这两个方法必须在同步块代码里面调用。

 

核心问题场景:当一个线程获得当前实例的锁lock,并且进入了方法A,该线程在方法A没有释放该锁的时候,是否可以再次进入使用该锁的方法B?

不可重入锁:在方法A释放锁之前,不可以再次进入方法B

可重入锁:在方法A释放该锁之前,可以再次进入方法B

1)不可重入锁:

当线程在访问A方法的时候,获取的A方法的锁,在A方法锁释放之前不能够访问其他方法(如方法B)的锁。

不可重入锁模型:{}{}{}{}{}都是独立的访问每一个方法,加锁 - 释放;加锁 - 释放。。。

2)可重入锁:

当线程在访问A方法的时候,获取A方法的锁,然后访问B方法获取B方法的锁,并计数加1,以此类推可以访问完了以后依次解锁。

可重入锁模型:{{{{}}}} 每次都可访问另一个方法,且加锁计数器加1,完全释放锁为计数器等于0

 

二、可重入锁​

可重入锁,是指同一个线程可以重入上锁的代码段,不同的线程进入则需要进行阻塞等待。

Java的可重入锁有:reentrantLock(显式的可重入锁)、synchronized(隐式的可重入锁)

可重入锁诞生的目的就是防止死锁,导致同一个线程不可重入上锁代码段,目的就是让同一个线程可以重新进入上锁代码段。

设计可重入锁的示例代码

public class MyReentrantLock {
    boolean isLocked = false;   // 默认没有上锁
    Thread lockedBy = null; // 记录阻塞线程
    int lockedCount = 0;    // 上锁次数计数

    /**
     * 上锁逻辑
     */
    public synchronized void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();
        // 上锁了 并且 如果是同一个线程则放行,否则其它线程需要进入where循环进行等待
        while (isLocked && lockedBy != thread) { 
            wait();
        }
        isLocked = true; // 第一次进入就进行上锁
        lockedCount++; // 上锁次数计数
        lockedBy = thread; // 当前阻塞的线程
    }

    /**
     * 释放锁逻辑
     */
    public synchronized void unlock() {
        if (Thread.currentThread() == this.lockedBy) {
            lockedCount--; // 将上锁次数减一
            if (lockedCount == 0) {// 当计数为0,说明所有线程都释放了锁
                isLocked = false; // 真正的将释放了所有锁
                notify();
            }
        }
    }
}

 

1、可重入锁​ synchronized

package com.test.reen;

// 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁,synchronized和ReentrantLock都是可重入的
// 可重入降低了编程复杂性
public class WhatReentrant {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (this) {
					System.out.println("第1次获取锁,这个锁是:" + this);
					int index = 1;
					while (true) {
						synchronized (this) {
							System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
						}
						if (index == 10) {
							break;
						}
					}
				}
			}
		}).start();
	}
}

 

2、可重入锁​ reentrantLock

package com.test.reen;

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

// 演示可重入锁是什么意思
public class WhatReentrant2 {
	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					lock.lock();
					System.out.println("第1次获取锁,这个锁是:" + lock);

					int index = 1;
					while (true) {
						try {
							lock.lock();
							System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
							
							try {
								Thread.sleep(new Random().nextInt(200));
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							
							if (index == 10) {
								break;
							}
						} finally {
							lock.unlock();
						}

					}

				} finally {
					lock.unlock();
				}
			}
		}).start();
	}
}

可以发现,上面的两个可重入锁 synchronized 和 reentrantLock,都没发生死锁,可以多次获取相同的锁

注意点:ReentrantLock 和 synchronized 还不一样,ReentrantLock需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样,最好在 finally 中jin'x进行锁释放

 

三、不可重入锁

不可重入锁,是指同一个线程不可以重入上锁后的代码段

如下代码示例,是一个不可重入锁的逻辑过程。

代码示例:

public class Count{
    MyLock lock = new MyLock();

    public static void main(String[] args) throws InterruptedException {
        new Count().doSomeThing(); // 示例的main方法
    }
    public void doSomeThing() throws InterruptedException {
        lock.lock(); // 第一次上锁
        System.out.println("执行doJob方法前");
        doJob(); // 方法内会再次上锁
        lock.unlock(); // 释放第一次上的锁
    }
    public void doJob() throws InterruptedException {
        lock.lock();
        System.out.println("执行doJob方法过程中");
        lock.unlock();
    }
}

/**
 * 自定义锁
 */
class MyLock{
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException{
        while(isLocked){
            wait();
        }
        isLocked = true; // 线程第一次进入后就会将器设置为true,第二次进入是就会由于where true进入死循环
    }
    public synchronized void unlock(){
        isLocked = false;   // 将这个值设置为false目的是释放锁
        notify();           // 结束阻塞
    }
}

从上面代码,您会发现执行main方法控制台会打印 "执行doJob方法前",然后就会一直线程阻塞,不会打印 "执行doJob方法过程中",原因在于第一次上锁后,由于没有释放锁,因此执行第一次lock后isLocked = true,这个时候调用doJob()内部又一次调用了lock(),由于上个线程将isLocked = true,导致再次进入的时候就进入死循环。从而导致线程无法执行System.out.println("执行doJob方法过程中");这行代码,因此控制台只能打印 "执行doJob方法前"。这种现象就造成了不可重入锁

 

 

参考推荐:

Java 高级研发技术栈

Java 线程同步的七种方法

Python 多线程解析

Python学习入门(21)——线程

Redis + Lua 脚本实现复合操作