面试Java基础问题汇总 part2
synchronized关键字的底层原理
Synchronized
关键字的底层原理属于JVM层面。
Synchronized
关键字同步语句块使用的是monitorenter
和monitorexit
指令,其中monitorenter
指令指向同步代码块的开始位置,monitorexit
指令指示结束位置。
当执行monitorenter
指令时,线程试图获取monitor的所有权(monitor对象存在于每个Java对象的对象头中,synchronized所便是通过这种方式获取锁的,也解释了为什么Java中任意对象可以作为锁的原因)。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那么当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
ThreadLocal
ThreadLocal
用于实现每一个线程都有自己的专属本地变量。
如果创建了一个ThreadLocal
变量,那么访问这个变量的每个线程都会有这个变量的本地副本
Synchronized和ReentrantLock的区别
- 两个都是可重入锁。
-
synchronzied
依赖于JVM而ReentrantLock
依赖于API。 - 由于
synchronized
在JDK1.6进行过大量优化,所以性能已经不是选择标准。 -
ReentrantLock
比synchronized
增加了一些高级功能:-
ReentrantLock
可以通过lock.lockInterruptibly()
来实现中断线程对锁的等待,改为去做其他事。 -
ReentrantLock
可以指定是公平锁还是非公平锁(ReentrantLock(boolean fair)
);而synchronized
只能是非公平锁。 -
synchronized
关键字可以与wait()
和notify()/notifyAll
方法相结合可以实现等待/通知机制。ReentrantLock
也可以实现,需要借助Condition
接口和newCondition()
方法。线程可以注册在指定的Condition
实例中从而可以有选择性的进行线程通知,在调度上更加灵活。而synchronized
关键字notify()
方法被通知的线程是由JVM选择的,执行notifyAll()
方法时,相当于整个Lock对象中只有Condition实例,所有的线程都注册在它一个身上,notifyAll()
方法会通知所有处于等待的线程,这样造成很大的效率问题,而Condition实例的singal()
方法只会唤醒注册在该Condition实例上的线程。
-
volatile关键字
JDK1.2之前,Java的内存模型实现总是从主存(即共享内存中)读取变量,是不需要进行特别注意的。而在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器寄存器)中,而不是直接在主存中进行读写。这就可能造成了一个线程在主存中修改了一个变量的值,而另外一个线程还在继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。为了解决这个问题,我们可以声明变量为volatile,这指示JVM,这个变量是不稳定的,每次使用它都在主存中进行读取。
volatile
关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排。
并发编程的三个重要特性
- 原子性:一个代码片段,要么所有的操作全部得到执行并且不会受到任何因素的干扰而中断,要么全部都不执行。使用
synchronized
可以保证代码片段的原子性。 - 可见性:当一个变量对共享变量进行了修改,那么另外的线程都是立即能看到修改后的最新的值。
votaile
关键字可以保证共享变量的可见性。 - 有序性:代码在执行过程中的先后顺序,Java在编译器以及运行期间的优化,代码的执行顺序未必的就是编写代码时候的顺序。
volatile
关键字可以禁止指令进行重排序优化。
Synchronized关键字和Volatile关键字的区别
-
volatile
关键字是线程同步的轻量级实现,所以volatile
关键字性能肯定要比synchronized
关键字性能要好。 -
volatile
关键字只能用于变量而synchronized
关键字可以修饰方法以及代码块。 (Java SE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级所以及其他各种优化之后执行效率有显著提升,实际开发过程中使用synchronized关键字的场景还是更多一些。) - 多线程访问
volatile
关键字不会发生阻塞,而synchronized
关键字两者都能保证。 -
volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。 -
volatile
关键字主要用于解决变量在多个线程之间的可见性,而synchronized
关键字解决的是多个线程访问资源的同步性。
sleep()和wait()的共同点和不同点
- 两者都可以暂停线程执行。
-
sleep()
没有释放锁,而wait()
方法释放了锁。 -
wait()
通常用于线程间交互/通信,sleep()通常被用于暂停执行。 -
wait()
方法调用后,线程不会自动苏醒,需要别的线程调用同一对象的notify()
或者notifyAll()
方法。sleep()
方法执行完成后,线程会自动苏醒,或者可以使用wait(long timeout)
超时后线程会⾃动苏醒。
为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤run() ⽅法?
start()
方法会执行线程的相应准备工作,然后再执行run ()
方法的内容,这是真正的多线程工作,而直接运行run()
,会把run()
方法当作主线程下的普通方法执行,并不会在某个线程执行它,所以不是多线程工作。
总结: 调⽤ start()
⽅法⽅可启动线程并使线程进⼊就绪状态,⽽ run()
⽅法只是 thread 的⼀个普通⽅法调⽤,还是在主线程⾥执⾏。
双重校验所实现单例模式(线程安全)
单例模式是Java中最简单的设计模式之一,它属于创建性模式,提供了一种创建对象的最佳方式。
简单地说,单例模式涉及到一个单一的类,且该类负责自己创建自己的对象,同时需要确保只有单个对象被创建,并且提供了访问其唯一对象的方式,可以直接访问到,不需要实例化该类的对象
public class Singleton {
private volatile static Singlention uniqueInstance;
private Singleton() {}
public synchronized static Singleton getUniqueInstance() {
if(uniqueInstacne == null) {
synchronized (singleton.class) {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
volatile
关键字可以禁止JVM指令重排,保证多线程环境下也能正常运行。
synchronized 关键字的底层原理
synchronized 关键字底层原理属于 JVM 层⾯。
① synchronized 同步语句块的情况
synchronized 同步语句块的实现使⽤的是monitorenter
和monitorexit
指令,其中monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置
② synchronized 修饰⽅法的的情况
synchronized修饰的方法并没有monitorenter
指令和monitorexit
指令,取而代之的是ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法,JVM
通过该ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的方法调用。
JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍⼀下这些优
化吗?
这里做个引子,需要详细去了解,面试中很有可能的问题是,你了解java的锁吗,请介绍一下?
偏向锁、轻量级锁、自选锁、适应性自选锁、锁消除、锁粗化等减少锁操作的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。锁之呢个升级不能降级,这种策略是为了提高获得锁和释放锁的效率。
Synchronized 和 ReentrantLock的区别
- 两者都是可重入锁。
-
synchronized
依赖于JVM
而ReentrantLock
依赖于API
。 -
ReentrantLock
比synchronized
增加了一些高级功能。主要有三点:(1)等待可中断;(2) 可实现公平锁;(3)可选择性通知(锁可以绑定多个Condition) - 性能不是选择标准。
TCP、UDP协议的区别
类型 |
面向连接 |
传输可靠性 |
传输形式 |
传输效率 |
所需资源 |
应用场景 |
首部字节 |
---|---|---|---|---|---|---|---|
TCP |
面向连接 |
可靠 |
字节流 |
慢 |
快 |
要求通讯数据可靠(文件传输、邮件传输) |
20-60 |
UDP |
无连接 |
不可靠 |
数据报文段 |
快 |
少 |
要求通信速度快(域名转换) |
8个字节 |
TCP提供面向连接的服务。数据传送之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或者多播服务。
TCP的可靠性体现在传递数据之前,会有三次握手来建立连接,数据传递时,有确认、窗口、重传、拥塞控制机制,数据传送完毕后,还有断开链接。
TCP协议如何保证可靠传输 (重点)
- 应用数据被分割成TCP最适合发送的数据块。
- TCP给发送的每一个包进行编号,接收方对数据包进行排序,将有序数据传送给应用层。
- 校验和:TCP将保持它首部和数据的校验和。这是一个端到端的检验和,目的时检测数据在传输过程中的任何变化。如果校验和有差错,TCP将丢弃这个报文段并不确认收到此报文。
- TCP的接收端会丢弃重复的数据。
- 流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端能接纳的数据。当接受方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。
- 拥塞控制:当网络拥塞时,减少数据的发送。
- 停止等待协议:也是为了实现可靠传输的,它的基本原理就是每发完一个分组就- 停止发送,等待对方确认。在收到确认后再发下一个分组。 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段(超时重传)。
ARQ协议:也是为了实现可靠传输,基本原理时每发完一个分组就停止发送,等待对方确认。收到确认后再发下一个分组。
- 停止等待ARQ协议
- 连续ARQ协议
⾃动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之⼀。它通过使⽤确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送⽅在发送后⼀段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。
在浏览器输入一个url地址到显示页面的过程
- 浏览器查找域名的IP地址(DNS查找过程、浏览器缓存、路由器缓存、DNS缓存)。【对应DNS协议:获取域名对应IP】
- 浏览器向web服务器发送一个HTTP请求(该过程中cookies会随着请求发送给服务器)。
- 服务器处理请求(请求、处理请求及其参数、cookies,生成一个HTML相应)。
- 服务器返回一个HTML响应。
- 浏览器开始显示HTML。
2-5 包含协议: TCP:与服务器建立连接 IP:建立TCP协议时,需要发送数据,在网络层使用IP协议 OSPF:IP数据包在路由器之间,路由器选择使用OSPF协议(类似协议有RIP协议、IGRP(cisco私有)、EIGRP(cisco私有) ARP:路由器在与服务器通信时,需要将IP地址转化为MAC地址,该过程使用ARP协议 HTTP:TCP连接建立完成后,使用HTTP协议访问网页
- DNS解析
- TCP连接
- 发送HTTP请求
- 服务器处理请求并返回HTTP报文
- 浏览器解析渲染画面
- 连接结束
状态码
有些公司会问的很细,比如505是什么意思?
状态码 |
类别 |
原因短语 |
---|---|---|
1xx |
Information(信息性状态码) |
接收的请求正在处理 |
2xx |
Success(成功状态码) |
请求正常处理完毕 |
3xx |
Redirection(重定向状态码) |
需要附加操作以完成请求 |
4xx |
Clien Error(客户端错误状态码) |
服务器无法处理请求 |
5xx |
Server Error(服务器错误状态码) |
服务器处理请求出错 |
HTTP是不保存状态的协议,如何保存用户状态?
使用Session和Cookies。
Session和Cookies的区别
Cookies和Session都是用来跟踪浏览器用户身份的会话方式,应用场景不太一样。
Cookies一般用来保存用户信息: (1)保存上次登录信息,下次自动填充; (2)下次访问不需要重新登陆; (3)登录一次网站后访问同网站其他页面不需要重新登录。
Session主要作用通过服务端记录用户的状态(典型场景购物车)。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。 Cookie 存储在客户端中,⽽Session存储在服务器上,相对来说 Session 安全性更⾼。如果要在Cookie 中存储⼀些敏感信息,不要直接写⼊ Cookie 中,最好能将 Cookie 信息加密然后使⽤到的时候再去服务器端解密。
HTTP1.0和HTTP1.1的主要区别
- 长连接:HTTP/1.0使用短链接,每次请求都要建立一次连接;HTTP/1.1使用长连接,默认开启
Conection: keep-alive
。持续连接分为流水线方式和非流水线方式。流水线是指,客户端在收到HTTP响应报文前就能接着发送新的请求报文;非流水线则是指客户端收到响应之后才能发送下一个请求。 - HTTP/1.1新增了24个错误状态响应码。如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
- 缓存处理:HTTP/1.1新增了更多的缓存头来控制缓存控制策略。
- 带宽优化及网络连接的使用:HTTP/1.1允许值请求资源的某个部分,以此节省带宽。
HTTP和HTTPS的区别
- 端口:HTTP使用80,HTTPS使用443。
- 安全性和资源消耗:HTTP运行在TCP之上,所有传输的都是明文,客户端和服务器无法验证对方身份。HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,传输内容经过对称加密,但对称加密的密匙用服务器方的证书进行了非对称加密。
- 对称加密:密匙只有一个,加密解密为同一个密码;
- 非对称加密:密匙成对出现(分为公匙和私匙,且根据公匙无法推知私匙,加密解密使用不同密匙)。
参考资料
JavaGuide面试突击版,百度可得最新版。这里修正了原文的一些错误和不严谨的地方。
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 如何在Jetson nano上同时编译TensorRT与Paddle Lite框架
- 一文概览无监督聚类算法有多少 | 算法基础(10)
- 算法集锦(28)| 智能医疗 | 血液细胞分类算法
- 基于飞桨复现 CVPR2018 Relation Net的全程解析
- 如何将DB2数据库转换成Oracle数据库,这一篇告诉你
- MySQL 8.0 安装部署3个注意事项
- 打卡群刷题总结0802——反转链表 II
- 打卡群刷题总结0801——解码方法
- 动手写简单的嵌入式操作系统二
- C++核心准则E.6:使用RAII防止资源泄露
- C++核心准则E.12: 当不可能或不愿意通过抛出异常退出函数时使用noexcept
- C++核心准则E.13: 直接拥有一个对象所有权时永远不要抛出异常
- C++核心准则E.14:使用根据目的设计的用户定制类型异常(非内置类型)
- C++核心准则E.16:析构函数,内存释放和swap操作永远不能失败
- Eclipse配合GDB和jlinkGDBServer仿真调试STM32