在这中间可以添加N行代码,但必须保证s引用的指向不变,最终将输出变成abcd——反射
package com.Test0720;
import java.lang.reflect.Field;
public class Test1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String s = new String("abc");
//在这中间可以添加N行代码,但必须保证s引用的指向不变,最终将输出变成abcd
//反射
Field value = s.getClass().getDeclaredField("value");
value.setAccessible(true);
value.set(s,"abcd".toCharArray());
System.out.println(s);
}
}
输出结果:
(2)看代码回答问题二
package com.Test0720;
public class Test2 {
public static void main(String[] args) {
String s1 = new String("abc");//2个对象
String s2 = "abc";
//s1==s2? true or false
System.out.println(s1 == s2);
String s3 = s1.intern();
//s2==s3? true or false
System.out.println(s2 == s3);//true
//String对象的intern方法,首先会检查字符串常量池中是否存在“abc”,
// 如果不存在,则把“abc”添加到字符串常量池中,并返回该字符串常量使用。
}
}
输出结果:
(3)看代码回答问题三
在Interger类中,存在一个静态内部类integerCache,该类中存在一个integer cache[],并且存在一个static块,会在加载的时候执行,会将-128-127这些数字提前生成integer对象,并缓存在cache数组中,当我门在定义integer数字时,会调用integer的valueOf方法,valueOf方法会判断所定义的数字是否在-128到127之间,如果存在则直接从cache数组中获取integer对象,如果超越,则生成一个新的integer对象。
package com.Test0720;
public class Test3 {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
//i1==i2? true or false
System.out.println(i1 == i2);//true
Integer i3 = 128;
Integer i4 = 128;
//i3==i4? true or false
System.out.println(i3 == i4);//false
//在Interger类中,存在一个静态内部类integerCache,该类中存在一个integer cache[],并且存在一个static块,
// 会在加载的时候执行,会将-128-127这些数字提前生成integer对象,并缓存在cache数组中,当我门在定义integer数字时,
// 会调用integer的valueOf方法,valueOf方法会判断所定义的数字是否在-128到127之间,
// 如果存在则直接从cache数组中获取integer对象,如果超越,则生成一个新的integer对象
}
}
输出结果:
(4)String、StringBuffer、StringBuilder的区别
- String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可变的
- StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高
package com.Test0720;
public class Test4 {
public static void main(String[] args) {
String s = "abc";
s="abcd";
StringBuilder builder = new StringBuilder(s);
builder.append("d");
StringBuffer buffer = new StringBuffer(s);
buffer.append("d");
//1、String是不可变的,如果尝试去修改,会新生成一个字符串对象,StringBuilder和StringBuffer是可变的
//2、StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高
}
}
(5)ArrayList和LinkedList区别
- 首先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
- 由于底层数据结构不同,他们所适⽤的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同
- 另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以 LinkedList还可以当做队列来使用
package com.Test0720;
import java.util.ArrayList;
import java.util.LinkedList;
public class Test5 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();//数组
arrayList.add("1");//扩容
arrayList.add(3,"1");//扩容 移动
LinkedList linkedList = new LinkedList();//链表
linkedList.add("1");
linkedList.add(3,"1");
System.out.println(arrayList);
System.out.println(linkedList);
//1.首先,他们底层数据结构不同,ArrayList底层是基于数据实现的,LinkedList底层是基于链表实现的
//2.由于底层数据结构不同,他们所使用的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加,删除时间复杂度不同
//3.另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当做队列来使用
}
}
(6)CopyOnWriteArrayList的底层原理是怎样的
- 首先CopyOnWriteArrayList内部也是用过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
- 并且,写操作会加把锁防止出现并发写入丢失数据的问题
- 写操作结束之后会把原数组指向新数组
- CopyOnWriteArrayList允许写操作时读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占用内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景。
注意:ArrayList会导致线程不安全
package com.Test0720;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
public class Test6 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("zhouyu");
arrayList.add("tuling");
//会导致线程不安全
CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
list.add("zhouyu");
String s = list.get(1);
System.out.println(list);
System.out.println(arrayList);
}
}
(7)HashMap的扩容机制原理 1.7版本:
- 先生成新数组
- 遍历老数组中的每个位置上的链表上的每个元素
- 取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
- 将元素添加到新数组中去
- 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
- 先生成新数组
- 遍历老数组中的每个位置上的链表或红黑树
- 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
- 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
- 统计每个下标位置的元素个数
- 如果该位置下的元素个数超过了8,则⽣成⼀个新的红黑树,并将根节点的添加到新数组的对应位置
- 如果该位置下的元素个数没有超过8,那么则生成⼀个链表,并将链表的头节点添加到新数组的对应位置
5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
package com.Test0720;
import java.util.HashMap;
public class Test7 {
public static void main(String[] args) {
HashMap hashMap = new HashMap<>();
hashMap.put("zhouyu","zy");
System.out.println(hashMap);
//HashMap的扩容机制原理
}
}
输出结果:
(8)谈谈ConcurrentHashMap的扩容机制: 1.7版本:
- 1.7版本的ConcurrentHashMap是基于Segment分段实现的
- 每个Segment相对于⼀个⼩型的HashMap
- 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
- 先生成新的数组,然后转移元素到新数组中
- 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
- 1.8版本的ConcurrentHashMap不再基于Segment实现
- 当某个线程进行put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进行扩容
- 如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进⾏扩容
- ConcurrentHashMap是支持多个线程同时扩容的
- 扩容之前也先生成⼀个新的数组
- 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或多组的元素转移工作
(9)ThreadLocal的底层原理:
- ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意⽅法中获取缓存的数据
- ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
- 如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,
- 但线程池中的线程不会回收,而线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从⽽出现内存泄漏,
- 解决办法是,在使用了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,手动清楚Entry对象
- ThreadLocal经典的应用场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递,线程之间不共享同⼀个连接)
package com.Test0720;
public class Test9 {
private static ThreadLocal s = new ThreadLocal<>();
public static void main(String[] args) {
s.set("zhouyu");
String s1 = Test9.s.get();
s.remove();
}
}
(10)如何理解volatile关键字:
- 在并发的领域中,存在三大特性:原子性,有序性,可见性。
- volatile关键字用来修饰对象的属性,在并发环境下可以保证这个属性的可见性。
- 对于加了volatile关键字的属性,在这个属性进行修改时,会直接将CPU高级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性。
- 底层是通过操作系统的内存屏障来实现的,由于使用了内存屏障,所以会禁止指令重排,所以也就同时保证了有序性,在很多并发场景下,如果用好volatile关键字可以很好的提高执行效率。



