17-理解自旋锁,死锁与重入锁

本讲我们来了解我们之前所遇到的几个锁的概念,就是自旋锁、死锁、重入锁。

我们首先来看重入锁,也就是锁重入,什么意思呢?我们之前,我们用到的synchronized就是一个重入锁。那么,什么是重入锁呢?先说非重入锁,我们知道,当多个线程来访问一个方法的时候,比如说这个方法上已经加了一个synchronized,多个线程来进行访问的时候,

那么,显然,当一个线程拿到我们的锁之后,那么,其他的线程就需要等待,竞争锁,那么,当第一个线程执行完毕释放了这个锁之后,那么,其他的线程才能够再进来,那么这就是说锁是不能够让所有的线程都进来的,只能有一个线程进来,然后其他的线程都在外面等着,

那么,锁重入是什么意思呢?之所以能把其他的线程挡在外面,就是因为当一个线程拿到了这个锁之后,其他的线程就不再能够拿到这个锁了,那么,锁重入是什么意思呢?两个方法,

这两个方法都是使用同一个对象去锁的,比如说我们在同一个实例里面都用的synchronized,加上了这把锁,那么,第一个线程在这个方法中

调用另一个方法

那么,也就是说,第一个线程进来之后

它拿到了synchronized的这把锁,然后,接着,这个方法调用这个方法的时候

那么,

这个方法也加了锁,那么,这个时候,

它就可以直接的来访问这个方法,而不是说,

这个对象的锁已经被这个线程拿到了,它就就不能再拿

这把锁了。而是,也可以来进行访问这个方法的,这就是所谓的锁的重入。我们见到的synchronized、包括我们以后要讲的lock,等等,都是一个重入锁,这里说锁的重入和重入锁,那么,能够让线程重入的锁,就称之为重入锁,这个过程叫做锁的重入。我们来举个例子看一下什么情况下会出现锁的重入呢?为什么会引入锁重入这么一个概念呢?

当然了,这里没有线程安全性问题,但是为了演示锁的重入,我们给这两个方法都加上synchronized,我们刚才也说到了,synchronized就是一个重入锁,我们知道,把synchronized加到方法上,那么,它所锁的对象就是当前类的实例。然后,我们在a()方法中去调用b()方法。

那么,按照我们当前的逻辑来判断,这两个synchronized锁的都是当前类的实例,那么,当第一个线程进来之后拿到了这把锁,拿到了当前Demo这个类的实例所加的这把锁,那么,在它没有释放的时候,那么当前Demo这个类的实例所加的这把锁已经被这个线程拿到了,那么,这个线程还能再进入到另外一个被当前Demo这个类的实例所加的这把锁的一个方法吗?显然是不会的,因为锁已经被拿到了,所以,这样如果一个线程进来访问的话,那么,很容易就产生了一个死锁,那么死锁的概念我们后面会说,但是并不是像我们所想的这样,它并不会出现死锁的问题,而是,这个b()方法是可以进行执行的,那么,我们来写一个例子来看一下,

我们这里需要一个线程任务,

在这个线程任务中干什么呢?我们实现一个Runnable接口,

在Runnable接口里面我们来实例化Demo对象并且调用它的方法,

那么,按照我们之前所分析的,如果锁是不可重入的,那么,这个时候b()方法显然是需要不停的等待的,一直等待当这个线程把这把锁释放掉之后才能进入到b()方法,那么,现在我们就来看是不是这样的,

我们发现很快就执行出来结果了。这就是关于锁的重入。大家可能有一个疑问,就是说,现在是同一个线程来访问a()方法的时候去调用b()方法,那么,如果说不同的线程拿同一把对象加锁,然后接着,就是说,当第一个线程调用a()方法的时候,也就是说,拿到了Demo这个对象所持有的锁,那么,第二个线程调用b()方法会不会等待呢?其实答案是显而易见的,

让多个线程去进行访问,那么,当调用a()方法的时候,

当a()方法没有执行完毕,接着调用b()方法,或者先调用b()方法,b()方法还没有调用完毕,接着调用a()方法的时候,那么,方法能不能被调用到?为了保证万无一失,我们让它等待

就是保证这个方法还没有执行完毕,不管是先调用谁,因为这两个是先调用a()还是先调用b(),我们是没有办法确认的

但是,a()和b()加的synchronized都是同一个,我们来执行,

我们发现,当调用a()的时候,其实是没有调用b()的,所以,这就是我们的synchronized,通过这个例子,想必咱大家对synchronized应该有一个更深入的了解。然后,我们再来举一个例子,

我们发现,a,b马上就打印了,并没有停顿,也就是说,锁的重入,就是说,当不同的对象,那么synchronized锁的肯定就是不同的对象了,因此也就锁不住这两个线程了,如果是使用同一个对象的话,才能够锁的住,而,当同一个线程拿到同一个对象的锁的时候,它是可以重入的,这也就避免了死锁问题,好了,关于锁的重入,就说到这里。

下面说自旋锁。什么是自旋锁呢?这里还是只给大家提概念,自旋就是自己在不停的旋转,那么旋转旋的是谁呢?我们可以这样去理解,旋的是CPU的时间片,也就是说空转CPU。就是说这个线程在这里不停的自己去while(true),一直等待另外的线程执行完毕,或者说等待被唤醒之后,那么,自旋锁到我们讲wait、notify、自定义锁的时候,我们再来去进行详细的讲解,这里只要知道,我们在了解synchronized轻量级锁的时候,当一个线程拿到了复制对象头中的信息到栈帧中,之后,另外一个线程如果再想获取的话,那么,它就必在这里不停的自旋,等待第一个线程把方法体中的内容执行完毕之后,它才能自己再去执行,也就是说,当第一个线程没有执行完毕的时候,这个线程一直在自旋等待。这是关于自旋的问题。在这里给大家举个非常简单的例子。

我们开多个线程,当多个线程同时执行完毕之后,我们再来打印一句话,就打印一句“所有线程执行完毕”,就干这么一件事,其实这里面就有自旋的成分在里面,当然我们后面学了其他的方式之后,显然就不再去使用自旋了,而是使用信号量,这个先不讲。我们首先要创建多个线程,

一个五个线程,我们需要在线程执行完毕之后打印一句话,

我们发现,已经说执行完毕了,为什么后面还会执行呢,就像开玩笑说,谁先睡着,谁睡着了说一声,那么,他说了,他是没有睡着的,也就是,这里打印“所有的线程执行完毕了”,显然是没有执行完毕,所以,怎么办呢?我们如何才能让,直到线程执行完毕才打印这句话,

这个是当前活动线程的数量,我们知道主线程在执行,也就是说,除掉主线程以外,还有还有其他的线程在执行,当活动线程只剩下一个主线程的时候,我们就认为所有的线程就执行完毕了,也就可以打印这句话了,于是我们就判断,这个虽然大家觉得比较简单,但是我们到后面来自己实现锁的时候,这些都是一个原理性的知识,

大家想一下,这会出现什么问题呢,显然这一话很有可能就打印不出来,

这是为什么呢?因为这几个线程是并行执行的,当执行到这段if代码的时候,它还不等于1呢,有同学可能说,这个好办,写成while

还是不对,用while了,为什么还是不行呢?先看while条件成不成立,显然是不成立的,也就是说,当它不等于1的时候,也就是说这个程序没有执行完毕,这就是一个自旋,

就在这里等待,空转CPU,当等待完毕,当它等于1的时候,也就相当与我们的程序就执行完毕了,

所有线程的信息都打印完了才打印了这句话,这就是一种自旋,这个例子说自旋的问题的时候是比较贴切的,但是我们如果想等待几个线程执行完毕之后再干一件事的话,我们显然不能用这种方式,因为,垃圾回收线程等等各种线程也是会有的,而且,我们判断线程个数的话,不能够这么判断

在实际应用中,这种判断线程个数的方式基本上是不行的,只要知道自旋就是这么一回事就可以了。好了,这就是关于自旋锁,后面我们还会再用到它。

下面我们讲死锁。死锁之前也提到过了,这里再来给大家进行一个说明,首先,什么是死锁?大家都已经比较清楚了,就是说,当一个线程永远的持有一把锁,并且其他线程都尝试获得这把锁的时候,那么,也就发生了死锁,那么,我们来举个例子,我们后面可能会经常提到死锁这个问题,无非就是争夺资源,我们之前也提到过哲学家进餐的问题,他拿他需要的,他拿他需要的,结果互相都不释放,于是互相都等待,也就出现了死锁问题,对于死锁的解决,Java虚拟机对死锁的解决能力还是比较弱的,第一,死锁并不一定每次都会发生,第二,死锁发生之后,在Java虚拟机中一定是这个线程就死了,不会说重新再给它通过一些手段再把它给释放掉或者怎么着处理,相比较Java虚拟机来讲,数据库系统就做的是非常好的,数据库中牵扯到的并发是非常的多的,优秀的数据库当它发生死锁之后,它能够把这个死锁的资源给释放掉,并且让这个线程重新恢复起来,非常的强大,这是数据库相关的内容,我们这里就不再深入的去了解它了,那么,我们这里就来研究一下死锁这个问题。

我们先来弄两把锁

然后,再来两个方法,a()方法中不干什么事,锁就行了,

然后,接着

然后,第二个方法,b()方法,

这种情况下就会出现死锁问题。

现在有两个线程,同时,线程一执行a()方法拿到了obj1锁,线程二执行b()方法拿到了obj2锁,现在线程一没有释放obj1锁,于是,第二个线程来请求obj1锁,那么就要等待

现在线程二在等待着,obj2锁肯定没有释放,于是

线程一又来请求obj2锁,所以线程一也处于等待状态,所以,这样就导致了死锁问题。那么,我们来演示一下这个问题,首先来两个线程任务,一个运行b()方法,一个运行a()方法,

在这里面非常的简单,当然得是同一个对象,不同的对象就会产生不同的实例,我们有没有有没有必要用同一个实例呢?因为我们没有在方法上加锁,但是还是要用同一个实例,如果说你不用同一个实例,那么,obj1和obj2也会不同,

所以,我们还是必须要要使用同一个实例。

然后我们在这个线程中调用a()方法

然后在另一个线程中调用b()方法,

然后我们来执行,

发现两个都打印了,没有说的产生死锁,

再次执行

程序卡在这里了,产生了死锁。

这就是死锁,这种问题在我们的开发过程中并不常见,但是,一旦遇到,这个线程就挂了。

刚刚是不是产生了死锁了呢?我们来看一下,

这就是jconsole检测死锁。死锁就说完了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值