2022-04-03 synchronized 部分原理+常量池部分问题+自动装箱/http 部分问题
部分整理参考来自
https://blog.csdn.net/zzti_erlie/article/details/86587263?utm_source=app&app_version=5.3.0
https://blog.csdn.net/zzti_erlie/article/details/103997713?utm_source=app&app_version=5.3.0
新十道
- synchronized关键字可以实现什么类型的锁?
jdk1.6 版本以后,锁的升级使得 synchronized 得到了一个质的优化。实现锁的种类也更加丰富
原本 synchronized 实现的锁:
- 悲观锁:synchronized关键字实现的是悲观锁,每次访问共享资源时都会上锁。
- 非公平锁:synchronized关键字实现的是非公平锁,即线程获取锁的顺序并不一定是按照线程阻塞的顺序。
- 独占锁或者排他锁:synchronized关键字实现的是独占锁,即该锁只能被一个线程所持有,其他线程均被阻塞。
1.6 之后增加:
- 可重入锁:synchronized关键字实现的是可重入锁,即已经获取锁的线程可以再次获取锁。
- 轻量级锁:当短时间,线程竞争较少的情况下,会优先启动轻量级锁,效率更高
- 偏向锁:某一线程拥有synchronized 锁对象后,synchronized 会偏向于该线程,使得下次该线程获得锁更容易。
- 偏向锁原理和升级过程
偏向锁过程:当线程1访问代码块并获取锁对象时,使用 cas 加锁成功时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,线程一再次主动获取该锁时,则无需使用CAS来加锁、
解锁:
- 改变偏向:如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程可以改变偏向。如果线程1 不再使用该锁对象,且线程 2 多次索取该锁 那么将锁对象状态设为无锁状态,重新偏向新的线程。
- 锁升级:如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,
- 为什么要引入轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,所以在几个线程轮流来获取锁的情况下,使用轻量级锁就足够了。
- 轻量级锁原理和升级过程
线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;此时完成的偏向锁的上锁。
此时如果线程 2 来竞争锁,进行 cas 获取锁对象上的锁记录地址,但是发现已经被线程一占用了,此时他就会进行锁自旋。(不断 cas)
- 如果在自旋过程中,线程一放弃了对锁的占用,线程二获取锁,锁变为轻量级锁
- 而自旋是有次数限制的,自旋到了还没有释放锁,就会发生锁膨胀,编程重量级锁,将所有没拥有锁的线程都放到阻塞队列里。
- 为什么要有常量池?
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,== 比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
- 关于常量池的一些问题
- Java有8种基本数据类型
整数类型:byte,short,int,long。包装类型为Byte,Short,Integer,Long
浮点类型:float、double。包装类型为Float,Double
字符类型:char。包装类型为Character
布尔类型:boolean。包装类型为Boolean
8种包装类型中除了Float,Double没有实现常量池,剩下的都实现了
- 什么是自动装箱,自动拆箱
自动装箱指的就是将原始的 java 类值变为一个对象。自动拆箱与其相反。
- Integer 类常量池引发的问题
主要的问题是,Integer 在[-128,127]
里对象是直接取自常量池的。
public static Integer valueOf(int i) {
// i的取值范围为[-128,127]
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
而超过这个范围,就要创建一个新的对象。
所以:
Integer a1 = 40;
Integer a2 = 40;
// true
System.out.println(a1 == a2);相等
Integer a3 = 200;
Integer a4 = 200;
// false
System.out.println(a3 == a4);//不相等
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
// true
System.out.println(str1 == str2);
// false
System.out.println(str1 == str3);
// false
System.out.println(str3 == str4);
解释是这样的:
采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"abc"这个对象
如果不存在,则在字符串池中创建"abc"这个对象,然后将池中"abc"这个对象的地址赋给str1,这样str1会指向池中"abc"这个字符串对象
如果存在,则不创建任何对象,直接将池中"abc"这个对象的地址返回,赋给str2。因为str1、str2指向同一个字符串池中的"abc"对象,所以结果为true。
而如果是 new 的:
无论常量池有没有都在堆里创建,常量池没有的话给常量池也创建一个对应的。
采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"abc"这个字符串对象,
如果没有,则首先在字符串池中创建一个"abc"字符串对象,然后再在堆中创建一个"abc"字符串对象,然后将堆中这个"abc"字符串对象的地址赋给str3
如果有,则不在池中再去创建"abc"这个对象了,直接在堆中创建一个"abc"字符串对象,然后将堆中的这个"abc"对象的地址赋给str4。这样,str4就指向了堆中创建的这个"abc"字符串对象;
复习五道
-
什么是HTTP报文?
Http报文就是客户端和服务端之间传送的数据块
-
HTTP报文由哪三部分组成?
HTTP报文由起始行,头部,主体组成,其中起始行是对该报文进行的描述,头部是对报文的属性进行的描述,主体则是数据的内容。
-
HTTP报文分为哪两类?
HTTP报文分为请求报文和响应报文,当客户端给服务器发送请求时,发送的报文就是请求报文,当服务器端給客户端返回请求时,返回的请求就是响应报文。
-
HTTP常见的请求方法有哪些?
常见的请求方式有
get请求:向服务器端发送报文,
post请求:向服务器端发送待处理的数据请求,
HEAD请求:获取服务器端制定的报文头部信息,
PUT请求:向服务器端发送请求并且更改制定的信息,
DELETE请求:删除指定的报文数据
-
HTTP的状态码分为哪几类?
100-101(2个)代表有提示信息,
200-206:代表请求成功 ,
300-305:重定向或者转发
400-415:客户端方出现了错误
500-505 :服务端出现了错误