锁和监视器之间的区别 – Java并发
在面试中你可能遇到过这样的问题:锁(lock)和监视器(monitor)有什么区别?
嗯,要回答这个问题,你必须深入理解Java的多线程底层是如何工作的。
简短的答案是,锁为实现监视器提供必要的支持。详细答案如下。
锁(lock)
逻辑上锁是对象内存堆中头部的一部分数据。JVM中的每个对象都有一个锁(或互斥锁),任何程序都可以使用它来协调对对象的多线程访问。如果任何线程想要访问该对象的实例变量,那么线程必须拥有该对象的锁(在锁内存区域设置一些标志)。所有其他的线程试图访问该对象的变量必须等到拥有该对象的锁有的线程释放锁(改变标记)。
一旦线程拥有一个锁,它可以多次请求相同的锁,但是在其他线程能够使用这个对象之前必须释放相同数量的锁。如果一个线程请求一个对象的锁三次,如果别的线程想拥有该对象的锁,那么之前线程需要 “释放”三次锁。
Java中显示锁的使用语法如下:
…
private Lock bankLock = new ReentrantLock();
…
public double getTotalBalance()
{
bankLock.lock();
try
{
double sum = 0; for (double a : accounts)
sum += a; return sum;
}
finally
{
bankLock.unlock();
}
}
1) 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
2) 锁可以管理试图进入被保护代码的线程
3) 锁可以拥有一个或者多个相关的条件对象
4) 每个条件对象管理那些已经进入被保护的代码段,但还不能运行的线程
Lock和Condition接口为程序设计人员提供了高度的锁定控制。然后大多数情况下,并不需要这样的控制,并且可以使用一种嵌入Java语言的内部机制。从1.0版本开始,Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就说,要调用该方法,线程必须获得内部的对象锁。
内部锁的一般用法如下:
public synchronized void transfer(int from, int to, double amount) throws InterruptedException
{
while (accounts[from] < amount)
wait();
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
notifyAll();
}
可以看到,用synchronized关键字来编写代码简洁得多。当然要理解这一代码,你必须了解每一个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程
然而,讲了这么多,实际上推荐最好优先使用BlockQueue,Excutor,同步集合等,然后再是synchronized关键字,最才是Lock/Condition
监视器(Monitor)
监视器是一中同步结构,它允许线程同时互斥(使用锁)和协作,即使用等待集(wait-set)使线程等待某些条件为真的能力。
互斥
使用比较形象的说明,监视器就像一个包含一个特殊房间(对象实例)的建筑物,每次只能占用一个线程。这个房间通常包含一些需要防止并发访问的数据。从一个线程进入这个房间到它离开的时间,它可以独占访问房间中的任何数据。进入监控的建筑被称为“进入监控监视器。”进入建筑内部特殊的房间叫做“获取监视器”。房间占领被称为“拥有监视器”,离开房间被称为“释放监视器。”让整个建筑被称为“退出监视器。”
当一个线程访问受保护的数据(进入特殊的房间)时,它首先在建筑物接收(entry-set)中排队。如果没有其他线程在等待(拥有监视器),线程获取锁并继续执行受保护的代码。当线程完成执行时,它释放锁并退出大楼(退出监视器)。
如果当一个线程到达并且另一个线程已经拥有监视器时,它必须在接收队列中等待(entry-set)。当当前所有者退出监视器时,新到达的线程必须与在入口集中等待的其他线程竞争。只有一个线程能赢得竞争并拥有锁。
这里没有wait-set的事。
合作
一般来说,只有当多个线程共享数据或其他资源时,互斥才是重要的。如果两个线程不处理任何公共数据或资源,它们通常不能互相干扰,也不需要以互斥的方式执行。尽管互斥有助于防止线程在共享数据时互相干扰,但合作有助于线程共同努力实现一些共同目标。
合作在当一个线程需要的数据改变为在一个特定的状态时很重要,另一个线程负责将数据该入状态,如生产者/消费者的问题,读线程需要缓冲去在一个“不空”的状态才可以从缓冲区中读取任何数据。如果读线程发现缓冲区为空,则必须等待。写线程负责用数据填充缓冲区。一旦写入线程完成了更多的写入操作,读线程可以进行更多的读取。它有时也称为“Wait and Notify”或“Signal and Continue”监视器,因为它保留对监视器的所有权,并继续执行监视区域(如果需要的话继续)。在稍后的时间内,通知线程释放监视器,等待线程重新恢复拥有锁。
这种合作需要entry-set和wait-set.。下面的图表将有助于您理解这种合作。

上图显示监视器为三个矩形。在该中心,一个大矩形包含一个线程,即监视器的所有者。在左边,一个小矩形包含entry set。在右边,另一个小矩形包含wait set。
监视器是由Per Brich Hansen和Tony Hoare提出的概念,Java以不精确的方式采用了它,也就是Java中的每个对象有一个内部的锁和内部条件。如果一个方法用synchronized关键字声明,那么,它表现的就像一个监视器方法。通过wait/notifyAll/nofify来访问条件变量
我希望上面内容将有助于你更深入地了解Java多线程,欢迎提出任何问题。
Happy Learning !!
锁和监视器之间的区别 – Java并发的更多相关文章
- java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)
目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...
- java锁与监视器概念 为什么wait、notify、notifyAll定义在Object中 多线程中篇(九)
在Java中,与线程通信相关的几个方法,是定义在Object中的,大家都知道Object是Java中所有类的超类 在Java中,所有的类都是Object,借助于一个统一的形式Object,显然在有些处 ...
- Java 并发:内置锁 Synchronized
摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程訪问某一共享.可变数据时,始终都不会导致数据破坏以及其它不该出现的结果. 而全部的并发模式在解决问题时,採 ...
- java并发笔记之四synchronized 锁的膨胀过程(锁的升级过程)深入剖析
警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是锁的升 ...
- Java并发编程原理与实战十五:手动实现一个可重入锁
package com.roocon.thread.ta1; public class Sequence { private MyLock lock = new MyLock(); private ...
- [Java并发] AQS抽象队列同步器源码解析--锁获取过程
要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...
- Java 并发系列之二:java 并发机制的底层实现原理
1. 处理器实现原子操作 2. volatile /** 补充: 主要作用:内存可见性,是变量在多个线程中可见,修饰变量,解决一写多读的问题. 轻量级的synchronized,不会造成阻塞.性能比s ...
- java并发编程系列原理篇--JDK中的通信工具类Semaphore
前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...
- java并发编程目录
java并发编程目录 Java多线程基础:进程和线程之由来 JAVA多线程实现的四种方式 Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition Jav ...
随机推荐
- MAC系统操作指令汇总
OSX 的文件系统 OSX 采用的Unix文件系统,所有文件都挂在跟目录 / 下面,所以不在要有Windows 下的盘符概念. 你在桌面上看到的硬盘都挂在 /Volumes 下. 比如接上个叫做 US ...
- OC继承以及实例变量修饰符
这里基本上跟java一样 所以就简单写几点要注意的: 1)OC与java一样都只支持单继承可以多层继承(java单继承多实现) 2) OC中的实例变量修饰符前要加 @ 例如 @private 例如下面 ...
- OC—Setter、Getter
一.本篇以Setter和Getter 来进行成员变量的赋值. 二.Setter 与 Getter 1. 命名规范 为对象中的某个实例变量赋值的方法称为修改方法,用来修改对象的状态这类修改方法称为set ...
- Oracle树形结构查询之prior的理解
--1 建表 create table 宇宙( 行星等级 number ,行星名称 varchar2(50) ,上级行星等级 number); --2 数据准备 insert into 宇宙 (行星等 ...
- FileZilla可以连接但是传输文件失败
在linux本地创建文件夹后,用FileZilla传输文件失败了. 如果用的是普通用户,创建的文件夹是属于root用户.所以不能传输,没有权限. 修改权限:sudo chown -R lishengn ...
- Windows下安装虚拟机和Linux
首先必须开启BIOS中的虚拟化 常见计算机BIOS快捷键: 联想E431 F1dell F2微星 DELETE 默认是开启HP pavilion notebook 15-ab527TX F9和F10m ...
- Spring BeanFactory源码学习
一.BeanFactory BeanFactory是Spring IOC容器的基础,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.BeanFactory提供了最基本的IOC容器的功能,即 ...
- JAVA printWriter中write()和println()区别
PrintWriter 的Write()方法和println()方法有何细微的区别? 最近学习JAVA网络编程,在服务器端和客户端产生一个Socket 后, 两边各自用getIputStream()和 ...
- wiringPi安装
wiringPi安装 更新软件,输入以下指令: sudo apt-get update sudo apt-get upgrade 通过GIT获得wiringPi的源代码 git c ...
- 面试(3)-java-se-java中的匿名内部类总结
java中的匿名内部类总结 匿名内部类也就是没有名字的内部类 正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口 实例1 ...