点击上方 “ 布衣码农 ” ,免费订阅~选择“ 设为星标 ”,第一时间免费获得更新~
![a856e2a3e19645a27fa229658d3efc53.png](https://i-blog.csdnimg.cn/blog_migrate/498527f634309e1516df5fa1a06536ec.png)
![34d2761ae278f6b093cb7856d40b6426.png](https://i-blog.csdnimg.cn/blog_migrate/3ba0e31faa591bc75435b8930f00bcbe.png)
锁
对于多线程编程模型,一个少不了的概念就是锁。 虽然叫做锁,但是其实相当于临界区大门的一个钥匙,那把钥匙就放到了临界区门口,有人进去了就把钥匙拿走揣在了身上,结束之后会把钥匙还回来。 只有拿到了指定临界区的锁,才能够进入临界区,访问临界区资源,当离开临界区时,释放锁,其他线程才能够进入临界区。 而对于锁本身,也是一种临界资源,是不允许多个线程共同持有的,同一时刻,只能够一个线程持有; 在前面的章节中,比如信号量介绍中,对于PV操作,就是对临界区资源的访问,下面的S就是临界区资源 Wait(S)和 signal(S)操作可描述为: wait(S): while (S<=0); S:=S-1; signal(S):S:=S+1;
但是上面的S,只是一种抽象的概念,在Java中如何表达?
换个问题就是:
在Java中是如何描述锁这种临界区资源的?
其实任何一个对象都可以被当做锁!
锁在Java中是对象头中的数据结构中的数据,在JVM中每个对象中都拥有这样的数据。
如果任何线程想要访问该对象的实例变量,那么线程必须拥有该对象的锁(也就是在指定的内存区域中进行一些数据的写入)。
当所有的其他线程想要访问该对象时,就必须要等到拥有该对象的锁的那个线程释放锁。
一个线程拥有了一个对象的锁之后,他就可以再次获取锁,也就是平常说的可重入,如下图所示,两个方法同一个锁。
假设methodA中调用了methodB(下面没调用),如果不可重入的话,一个线程获取了锁,进入methodA然后等待进入methodB的锁,但是他们是同一个锁。
自己等待自己,岂不是死锁了?
所以锁具有可重入的特性
![b4975394abbd8276b37d760fdc63f4ee.png](https://i-blog.csdnimg.cn/blog_migrate/cf900deedf164b23148856ed2ef82bc4.png)
监视器
对于对象锁,可以做到互斥,但是仅仅互斥就足够了吗? 比如一个同步方法(实例方法)以当前对象this为锁,如果多个线程过来,只有一个线程可以持有锁,其他线程需要等待,这个过程是如何管理的? 而且,在Java中,还可以借助于wait notify方法进行线程间的协作,这又是如何做到的? 其实在Java中还有另外一个概念,叫做监视器 《深入Java虚拟机》中如下描述监视器: 可以将监视器比作一个建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据。 一个线程从进入这个房间到它离开前,它可以独占地访问房间中的全部数据。 如果用一些术语来定义这一系列动作:进入这个建筑叫做“进入监视器”
进入建筑中的那个特别的房间叫作“获得监视器”
占据房间叫做“持有监视器”
离开房间叫做“释放监视器”
离开建筑叫做“退出监视器”
![60250118eee337f0a313ecd75226127b.png](https://i-blog.csdnimg.cn/blog_migrate/024cde437d9b3db47be64abae016c125.png)
![020d474921cc576c83c5fdb46195c082.png](https://i-blog.csdnimg.cn/blog_migrate/7ef7cf7725991e48203ef1dc7e7c8b87.png)
![97a31489ab379931778dc655bd5f817f.png](https://i-blog.csdnimg.cn/blog_migrate/8f752837134d183af9d34b46461d314c.png)
监视区域
对于监视器“房间”内的内容被称为监视区域,说白了监视区域就是监视器掌管的空间区域。 这个空间区域不管里面有多少内容,对于监视器来说,他们是最小单位,是原子的,是不可分割的代码,只会被同一个线程执行, 不管你多少并发,监视器会对他进行保障。 (对于开发者来说,你使用一个synchronized关键字就有了监视器的效果,监视器依赖JVM,而JVM依赖操作系统,操作系统则会进一步依赖软件甚至硬件,就是这样层层封装) 其实废话这么多,一个同步方法内(同步代码块)中所有的内容,就是属于同一个监视区域![f09ef9b365b8fea007ac86505b600028.png](https://i-blog.csdnimg.cn/blog_migrate/065f91b6929c058b95f60b809cabe191.png)
Java监视器逻辑
去医院就医时,有时需要进一步检查,现在你感冒有时都会让你查血  ̄□ ̄|| 大致的流程可能是这样子的:![24b4efe6fbbaf78ab02823881ad11876.png](https://i-blog.csdnimg.cn/blog_migrate/048ce94373b087522638edf8b63b3357.png)
![11cf699584d033b87298feea7aca1ccb.png](https://i-blog.csdnimg.cn/blog_migrate/a36677a943239a73dc4ed3bfe2ec3795.png)
当一个线程到达时,如果一个监视器没有被任何线程持有,那么可以直接进入监视器执行任务;
如果监视器正在被其他线程持有,那么将会进入“入口区域”,相当于走廊,在走廊排队等待叫号;
一个监视区域前后各有一个区域:入口区域,等待区域:
如果监视区域有线程,那么入口区域需要等待,否则可以进入;
监视区域内执行的线程可以通过命令进入等待队列,也可以将等待队列的线程唤醒,唤醒后的线程就相当于是入口区域的队列一样,可以等待进入监控区域;
进出监视器流程
![08e79610ea80c19f0cea7b32883e2650.png](https://i-blog.csdnimg.cn/blog_migrate/0e780692e157e9de9ca15529bdae06ea.png)
- 线程到达监控区域开始处,通过途径1进入入口区域,如果没有任何线程持有监控区域,通过途径2进入监控区域,如果被占用,那么需要在入口区域等待;
- 一个活动线程在监控区域内,有两种途径退出监控区域,当条件不满足时,可以通过途径3借助于等待命令进入等待或者顺利执行结束后通过途径5退出并释放监视器;
- 当监视器空闲时,入口区域的等待集合将会竞争进入监视器,竞争成功的将会进入监控区域,失败的继续等待(如果有等待的线程被唤醒,将会一同参与竞争);
- 对于等待区域,要么通过途径3进入,要么通过途径4退出,只有这两条途径,而且只有一个线程持有监视器时才能执行等待命令,也只有再次持有监视器时才能离开等待区;
- 对于等待区域中的线程,如果是有超时设置的等待,时间到达后JVM会自动通过唤醒命令将他唤醒,不需要其他线程主动处理;
关于唤醒
JVM中有两种唤醒命令,notify和notify all,唤醒一个和唤醒所有。 唤醒更多的是一种标志、提示、请求,而不是说唤醒后立即投入运行,前面也已经讲过了,如果条件再次不满足或者被抢占。 对于JVM如何选择下一个线程,依照具体的实现而定,是虚拟机层面的内容。 比如按照FIFO队列? 按照优先级? 各种权重综合? 等等方式 而且需要注意的是,除非是明确的知道只有一个等待线程,否则应该使用notify all,否则,就可能出现某个线程等待的时间过长,或者永远等下去的几率。语法糖
对于开发者来说,最大的好处就是线程的同步与调度这些是内置支持的,监视器和锁是语言附属的一部分,而不需要开发者去实现。 synchronized关键字就是同步,借助于他就可以达到同步的效果,这应该算是语法糖了。 对于同步代码块,JVM借助于monitorenter和monitorexit,而对于同步方法则是借助于其他方式,调用方法前去获取锁。 只需要如下图使用关键字 synchronized就好,这些指令都不需要我们去做。![a16fec6e02fc15e33554622d6aa87f19.png](https://i-blog.csdnimg.cn/blog_migrate/98ce11c84b70d6032239913ee869c50f.png)
有关锁的几个概念
死锁
锁死
活锁
饥饿
锁泄露
总结
Java在语言级别支持多线程,是Java的一大优势。 这种支持主要是线程的同步与通信,这种机制依赖的就是监视器,而监视器底层也是对锁依赖的,对象锁是对监视器的支撑。 也就是说,对象锁是根本,如果没有对象锁,根本就没有办法互斥,不能互斥的话,更别提协作同步了,监视器是构建于锁的基础上实现的一种程序,进一步提供了线程的互斥与协作的功能。 开发时比如synchronized关键字的使用,底层也会依赖到监视器,比如两个线程调用一个对象的同步方法,一个进入,那么另一个等待,就是在监视器上等待。 在JVM中,每一个类和对象在逻辑上都对应一个监视器, 其实想要理解监视器的概念,还是要理解管程的概念。 而 wait方法和notify notifyAll方法不就是管程的过程吗? 管程就是相当于对于线程进行同步的一个“IOC”,借助于管程托管了线程的同步,如果想要深入可以去研究下虚拟机。 毕竟对于任何一种语言来说,也都是一层层的封装最终转换为操作系统的指令代码,所有的这些功能在JVM层面看也毕竟都是字节码指令。 所以,说到这里,回到本文的最初问题上,“为什么wait、notify、notifyAll 都是Object的方法”? Java中所有的类和对象逻辑上都对应有一个锁和监视器,也就是说在Java中一切对象都可以用来线程的同步、所以这些管程(监视器)的“过程”方法定义在Object中一点也不奇怪~ 只要理解了锁和监视器的概念,就可以清晰地明白了~··················END··················
注:非技术讲解配图均来源于网络
期待分享
如果对你有用
可以点个 「在看」 或者分享到 「 朋友圈 」 哦
你「在看」吗?
↓↓