java并发编程ReentrantLock类和可重入锁概念,公平/非公平锁区别,可重入抛异常是否会释放锁

可重入概念

若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。可重入概念是在单线程操作系统的时代提出的。

就是说某个线程已经获得某个锁并且这个锁还没释放,可以再次获取该锁而不会出现死锁,不管是可重入锁还是不可重入锁,锁多少次,就要释放多少次

synchronized也是可重入锁

// 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁,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();
	}
}

一个简单的例子:用户名和密码保存在本地txt文件中,则登录验证方法和更新密码方法都应该被加synchronized,那么当更新密码的时候需要验证密码的合法性,所以需要调用验证方法,此时是可以调用的。
在这里插入图片描述

今天的重头ReentrantLock类(finally一定要手动释放锁)

// 演示可重入锁是什么意思
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();
	}
}

使用场景

1.如果发现该操作已经在执行中则不再执行

a、用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发。
b、用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发)。

以上两种情况多用于进行非重要任务防止重复执行,(如:清除无用临时文件,检查某些资源的可用性,数据备份操作等)

private ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) {  //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果 
        try {
            //操作
        } finally {
            lock.unlock();
        }
 }

2.如果发现该操作已经在执行,等待一个一个执行

这种比较常见大家也都在用,主要是防止资源使用冲突,保证同一时间内只有一个操作可以使用该资源。

但与synchronized的明显区别是性能优势(伴随jvm的优化这个差距在减小)。同时Lock有更灵活的锁定方式,公平锁与非公平锁,而synchronized永远是非公平锁

private ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
private ReentrantLock lock = new ReentrantLock(true); //公平锁

在这里插入图片描述

try {
   lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
    //其他操作
} finally {
   lock.unlock();
}

3.如果发现该操作已经在执行,等待执行。这时可中断正在进行的操作立刻释放锁继续下一操作。

synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题。(场景2的另一种改进,没有超时,只能等待中断或执行完毕)

这种情况主要用于取消某些操作对资源的占用。如:(取消正在同步运行的操作,来防止不正常操作长时间占用造成的阻塞)

try {
   	lock.lockInterruptibly();
    //操作
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

不公平锁与公平锁的区别

  • 公平情况下,操作会排一个队按顺序执行,来保证执行顺序。谁等的时间最长,谁就先获取锁。(会消耗更多的时间来排队)
  • 不公平情况下,是无序状态允许插队,jvm会自动计算如何处理更快速来调度插队。(如果不关心顺序,这个速度会更快)

源码里同时有公平和非公平,都是使用Sync,也就是使用之前讲过的aqs,复写tryAcquire来实现的
在这里插入图片描述
非公平锁
在这里插入图片描述

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

公平锁
在这里插入图片描述

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

重写的tryAcquire()方法区别在这
在这里插入图片描述
在这里插入图片描述
hasQueuedPredecessors 作用是判断当前线程前面有没有在排队的线程,有则返回true,否则返回false;换句话讲就是判断当前线程能不能获得锁,如果能够获得锁则返回false。如果队列为空,或是当前线程位于队列头部会返回false,其他情况返回true。

线程执行可重入锁时抛异常,是否会释放锁

看到一个例子,试一试

	@Slf4j
    public static class SynchronizedExceptionRunnable implements Runnable {

        private volatile boolean flag = true;

        @Override
        public void run() {
            synchronized (this) {
                if (flag) {
                    //让先启动的线程先执行异常方法methodB后,flag==false,并且抛出异常线程停止,直接释放锁,不会执行后面的代码;
                    methodB();
                } else {
                    //后启动的线程再获取锁,进入if-else,再获取锁执行methodA
                    methodA();
                }
                log.info("{}:if-else end!",Thread.currentThread().getName());
            }
        }

        public synchronized void methodA(){
            log.info("ThreadName:{}----methodA", Thread.currentThread().getName());
        }

        public synchronized void  methodB() {
            flag = false;
            log.warn("ThreadName:{}----methodB will throw a exception!",Thread.currentThread().getName());

            //如果把下面这行抛异常的代码注释掉,会执行下面的线程睡眠5秒和最后的日志代码
            //如果不注释,会抛出异常,不在执行后面的代码,并且释放锁,methodA方法就会执行
            int a = 1/0;


            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("ThreadName:{}----methodB End!",Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedExceptionRunnable runnable = new SynchronizedExceptionRunnable();

        Thread thread1 = new Thread(runnable,"杯子");
        Thread thread2 = new Thread(runnable,"人");
        thread1.start();
        thread2.start();
    }

报错返回
在这里插入图片描述
可以看到只跑了杯子这条打印前的代码,报错后面的代码不会跑
在这里插入图片描述
注释掉报错再跑
在这里插入图片描述
杯子会往下跑,并且sleep霸占线程5秒,之后才会跑人

总结:如果锁的计数器为2,执行过程中抛出异常,锁的计数器直接置为0,线程会直接停止并且会直接释放锁
在这里插入图片描述

参考:

阿里面试官:说一下公平锁和非公平锁的区别?

一文彻底理解ReentrantLock可重入锁的使用

java的可重入锁用在哪些场合?

线程执行SYNCHRONIZED同步代码块时再次重入该锁过程中抛异常,是否会释放锁

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值