01.Java基础问题汇总

45次阅读

共计 16086 个字符,预计需要花费 41 分钟才能阅读完成。

目录介绍

1.0.0.1 请手写 equal 方法,讲讲具体的原理?
1.0.0.2 请说下 String 与 StringBuffer 区别,StringBuffer 底部如何实现?String 类可以被继承吗,为什么?
1.0.0.3 String a=”” 和 String a=new String(“”) 的的关系和异同?String 的创建机制?
1.0.0.4 static 关键字可以修饰什么?static 使用的注意事项有哪些?static 关键字的特点?
1.0.0.5 为什么 Java 中的 String 是不可变的(Immutable)?字符串设计和实现考量?String 不可变的好处?
1.0.0.6 Hashcode 与 equal 区别,什么时候需要用到 hashcode?讲讲里面的原理。如何解决 Hash 冲突?
1.0.0.7 访问修饰符 public,private,protected, 以及不写(默认)时的区别?
1.0.0.8 静态变量和实例变量的区别?成员变量与局部变量的区别有那些?
1.0.0.9 如何实现对象克隆?深克隆,浅克隆分别说的是什么意思?
1.0.1.0 int 和 Integer 的区别?装箱、拆箱什么含义?什么时候装箱 / 拆箱?装箱和拆箱是如何实现的?
1.0.1.1 Object 有哪些公有方法?
1.0.1.2 final,finally,finalize 有什么不同?finally 什么情况下不会被执行?
1.0.1.3 为什么要使用通配符?上界通配符和下界通配符如何理解和注意要点?什么是无界通配符?
1.0.1.4 什么是泛型擦除,能否通过开发中实际案例说下?如何获取泛型的具体的类型【反射】?
1.0.1.5 如何验证 int 类型是否线程安全?那些类型是线程安全的?举一个线程安全的例子【AtomicInteger】?
1.0.1.6 Java 序列话中如果有些字段不想进行序列化怎么办?
1.0.1.7 有没有可能 两个不相等的对象有相同的 hashcode?当两个对象 hashcode 相同怎么办?如何获取值对象?
1.0.1.8 原始数据类型和引用类型局限性?为何要引用基本数据包装类?
1.0.1.9 new Integer(123) 与 Integer.valueOf(123) 有何区别,请从底层实现分析两者区别?

好消息

博客笔记大汇总【15 年 10 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 500 篇 [近 100 万字],将会陆续发表到网上,转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有博客将陆续开源到 GitHub!

1.0.0.1 请手写 equal 方法,讲讲具体的原理?

代码如下所示,如果是手写代码,一定要弄清楚逻辑思路!
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;
if (n == anotherString.count) {
int i = 0;
while (n– != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}

1.0.0.2 请说下 String 与 StringBuffer 区别,StringBuffer 底部如何实现?String 类可以被继承吗,为什么?

String 类的特点
String 的特点是一旦被创建,就不能被改变。注意是地址不能改变。StringBuffer 底层是可变的字节序列……

String 类可以被继承吗
看 String 源码可知,String 类被 final 关键字修饰了,所以不能被继承。这个地方可以说下 final 关键字作用。

String、StringBuffer 和 StringBuilder 的区别?

String 是字符串常量,而 StringBuffer、StringBuilder 都是字符串变量,即 String 对象一创建后不可更改,而后两者的对象是可更改的:
StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,这是由于 StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁
String 更适用于少量的字符串操作的情况,StringBuilder 适用于单线程下在字符缓冲区进行大量操作的情况,StringBuffer 适用于多线程下在字符缓冲区进行大量操作的情况
技术博客大总结

1.0.0.3 String a=”” 和 String a=new String(“”) 的的关系和异同?String 的创建机制?

区别

通过 String a=”” 直接赋值的方式得到的是一个字符串常量,存在于常量池;注意,相同内容的字符串在常量池中只有一个,即如果池已包含内容相等的字符串会返回池中的字符串,反之会将该字符串放入池中
通过 new String(“”) 创建的字符串不是常量是实例对象,会在堆内存开辟空间并存放数据,且每个实例对象都有自己的地址空间

String 的创建机制
由于 String 在 Java 世界中使用过于频繁,Java 为了避免在一个系统中产生大量的 String 对象,引入了字符串常量池。其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过 new 方法创建的 String 对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给 String 对象引用赋值的情况。

1.0.0.4 static 关键字可以修饰什么?static 使用的注意事项有哪些?static 关键字的特点?

可以用来修饰:成员变量,成员方法,代码块,内部类等。具体如下所示

修饰成员变量和成员方法

被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。
被 static 声明的成员变量属于静态成员变量,静态变量存放在 Java 内存区域的方法区。

静态代码块

静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行 (静态代码块—> 非静态代码块—> 构造方法)
该类不管创建多少对象,静态代码块只执行一次.

静态内部类(static 修饰类的话只能修饰内部类)

静态内部类与非静态内部类之间存在一个最大的区别:
非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。

静态导包 (用来导入类中的静态资源,1.5 之后的新特性):
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

static 使用的注意事项有哪些?

在静态方法中是没有 this 关键字的

静态是随着类的加载而加载,this 是随着对象的创建而存在。
静态比对象先存在。

静态方法只能访问静态的成员变量和静态的成员方法【静态只能访问静态, 非静态可以访问静态的也可以访问非静态的】

static 关键字的特点?

随着类的加载而加载
优先于对象存在
被类的所有对象共享
可以通过类名调用【静态修饰的内容一般我们称其为:与类相关的,类成员】

1.0.0.5 为什么 Java 中的 String 是不可变的(Immutable)?字符串设计和实现考量?String 不可变的好处?

不可变类 String 的原因

String 主要的三个成员变量 char value[],int offset, int count 均是 private,final 的,并且没有对应的 getter/setter;
String 对象一旦初始化完成,上述三个成员变量就不可修改;并且其所提供的接口任何对这些域的修改都将返回一个新对象;
技术博客大总结
是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。

字符串设计和实现考量?

String 是 Immutable 类的典型实现,原生的保证了基础线程安全,因为你无法对它内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可变,Immutable 对象在拷贝时不需要额外复制数据。
为了实现修改字符序列的目的,StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组,二者都继承了 AbstractStringBuilder,里面包含了基本操作,区别仅在于最终的方法是否加了 synchronized。
这个内部数组应该创建成多大的呢?如果太小,拼接的时候可能要重新创建足够大的数组;如果太大,又会浪费空间。目前的实现是,构建时初始字符串长度加 16(这意味着,如果没有构建对象时输入最初的字符串,那么初始值就是 16)。我们如果确定拼接会发生非常多次,而且大概是可预计的,那么就可以指定合适的大小,避免很多次扩容的开销。扩容会产生多重开销,因为要抛弃原有数组,创建新的(可以简单认为是倍数)数组,还要进行 arraycopy。

String 不可变的好处?

可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

1.0.0.6 Hashcode 与 equal 区别,什么时候需要用到 hashcode?讲讲里面的原理。如何解决 Hash 冲突?

Hashcode 与 equal 区别

equals() 比较两个对象的地址值是否相等;hashCode() 得到的是对象的存储位置,可能不同对象会得到相同值
有两个对象,若 equals() 相等,则 hashcode() 一定相等;hashcode() 不等,则 equals() 一定不相等;hashcode() 相等,equals() 可能相等、可能不等
使用 equals() 比较两个对象是否相等效率较低,最快办法是先用 hashCode() 比较,如果 hashCode() 不相等,则这两个对象肯定不相等;如果 hashCode() 相等,此时再用 equal() 比较,如果 equal() 也相等,则这两个对象的确相等。

什么时候需要用到 hashcode

同样用于鉴定 2 个对象是否相等的,java 集合中有 list 和 set 两类,其中 set 不允许元素重复实现,那个这个不允许重复实现的方法,如果用 equal 去比较的话,如果存在 1000 个元素,你 new 一个新的元素出来,需要去调用 1000 次 equal 去逐个和他们比较是否是同一个对象,这样会大大降低效率。hashcode 实际上是返回对象的存储地址,如果这个位置上没有元素,就把元素直接存储在上面,如果这个位置上已经存在元素,这个时候才去调用 equal 方法与新元素进行比较,相同的话就不存了,散列到其他地址上
技术博客大总结

如何解决 Hash 冲突

开放定址法:常见的线性探测方式,在冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表
链地址法:将有冲突数组位置生出链表
建立公共溢出区:将哈希表分为基本表和溢出表两部分,和基本表发生冲突的元素一律填入溢出表
再哈希法:构造多个不同的哈希函数,有冲突使用下一个哈希函数计算 hash 值

1.0.0.7 访问修饰符 public,private,protected, 以及不写(默认)时的区别?

类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。
区别如下:
作用域 当前类 同包 子类 其他
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×

1.0.0.8 静态变量和实例变量的区别?成员变量与局部变量的区别有那些?

静态变量和实例变量的区别

静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝。静态变量可以实现让多个对象共享内存。在 Java 开发中,上下文类和工具类中通常会有大量的静态成员。
实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它

成员变量与局部变量的区别

1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;
2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存
3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰但没有被 static 修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。

1.0.0.9 如何实现对象克隆?深克隆,浅克隆分别说的是什么意思?

有两种方式:

1. 实现 Cloneable 接口并重写 Object 类中的 clone() 方法;
2. 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。

注意问题:
基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用 Object 类的 clone 方法克隆对象。

代码如下所示
public class MyUtil {

private MyUtil() {
throw new AssertionError();
}

public static <T> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);

ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();

// 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream 对象的 close 方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源
}
}

class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person(“Hao LUO”, 33, new Car(“Benz”, 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand(“BYD”);
// 修改克隆的 Person 对象 p2 关联的汽车对象的品牌属性
// 原来的 Person 对象 p1 关联的汽车不会受到任何影响
// 因为在克隆 Person 对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}

通过反射获得泛型的实际类型参数

把泛型变量当成方法的参数,利用 Method 类的 getGenericParameterTypes 方法来获取泛型的实际类型参数

例子:
public class GenericTest {

public static void main(String[] args) throws Exception {
getParamType();
}

/* 利用反射获取方法参数的实际参数类型 */
public static void getParamType() throws NoSuchMethodException{
Method method = GenericTest.class.getMethod(“applyMap”,Map.class);
// 获取方法的泛型参数的类型
Type[] types = method.getGenericParameterTypes();
System.out.println(types[0]);
// 参数化的类型
ParameterizedType pType = (ParameterizedType)types[0];
// 原始类型
System.out.println(pType.getRawType());
// 实际类型参数
System.out.println(pType.getActualTypeArguments()[0]);
System.out.println(pType.getActualTypeArguments()[1]);
}

/* 供测试参数类型的方法 */
public static void applyMap(Map<Integer,String> map){

}

}

输出结果:
java.util.Map<java.lang.Integer, java.lang.String>
interface java.util.Map
class java.lang.Integer
class java.lang.String

1.0.1.0 int 和 Integer 的区别?装箱、拆箱什么含义?什么时候装箱 / 拆箱?装箱和拆箱是如何实现的?

int 和 Integer 的区别:基本数据类型、引用类型

Integer 是 int 的包装类,int 则是 java 的一种基本数据类型
Integer 变量必须实例化后才能使用,而 int 变量不需要
Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值
Integer 的默认值是 null,int 的默认值是 0

装箱、拆箱

装箱就是自动将基本数据类型转换为包装器类型
拆箱就是自动将包装器类型转换为基本数据类型

// 拆箱
int yc = 5;
// 装箱
Integer yc = 5;

jdk 中如何操作装箱、拆箱

在 JDK 中,装箱过程是通过调用包装器的 valueOf 方法实现的,而拆箱过程是通过调用包装器的 xxxValue 方法实现的(xxx 代表对应的基本数据类型)。
Integer、Short、Byte、Character、Long 这几个类的 valueOf 方法的实现是类似的,有限可列举,共享 [-128,127];
Double、Float 的 valueOf 方法的实现是类似的,无限不可列举,不共享;
Boolean 的 valueOf 方法的实现不同于以上的整型和浮点型,只有两个值,有限可列举,共享;

什么时候装箱 / 拆箱?
什么时候拆箱主要取决于:在当前场景下,你需要的是引用类型还是原生类型。若需要引用类型,但传进来的值是原生类型,则自动装箱(例如,使用 equals 方法时传进来原生类型的值);若需要的是原生类型,但传进来的值是引用类型,则自动拆箱(例如,使用运算符进行运算时,操作数是包装类型)。

装箱和拆箱是如何实现的
以 Interger 类为例,下面看一段代码来了解装箱和拆箱的实现
public class Main {
public static void main(String[] args) {
Integer y = 10;
int c = i;
}
}

然后来编译一下:

从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是 Integer 的 valueOf(int) 方法。而在拆箱的时候自动调用的是 Integer 的 intValue 方法。
因此可以用一句话总结装箱和拆箱的实现过程:装箱过程是通过调用包装器的 valueOf 方法实现的,而拆箱过程是通过调用包装器的 xxxValue 方法实现的。(xxx 代表对应的基本数据类型)。

1.0.1.1 Object 有哪些公有方法?

常用方法

equals():和 == 作用相似
hashCode():用于哈希查找,重写了 equals() 一般都要重写该方法
getClass():获取 Class 对象
wait():让当前线程进入等待状态,并释放它所持有的锁
notify()&notifyAll():唤醒一个(所有)正处于等待状态的线程
toString():转换成字符串

1.0.1.2 final,finally,finalize 有什么不同?finally 什么情况下不会被执行?

final 可以修饰类,方法,变量

final 修饰类代表类不可以继承拓展
final 修饰变量表示变量不可以修改
final 修饰方法表示方法不可以被重写

finally 则是 Java 保证重点代码一定要被执行的一种机制
可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。

finalize 是基础类 java.lang.Object 的一个方法
它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。

final 关键字深入理解

可以将方法或者类声明为 final,这样就可以明确告知别人,这些行为是不许修改的。
如果你关注过 Java 核心类库的定义或源码,有没有发现 java.lang 包下面的很多类,相当一部分都被声明成为 final class?在第三方类库的一些基础类中同样如此,这可以有效避免 API 使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。
使用 final 修饰参数或者变量,也可以清楚地避免意外赋值导致的编程错误,甚至,有人明确推荐将所有方法参数、本地变量、成员变量声明成 final。
final 变量产生了某种程度的不可变(immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值 final 变量,有利于减少额外的同步开销,也可以省去一些防御性拷贝的必要。

在以下 4 种特殊情况下,finally 块不会被执行:

1. 在 finally 语句块中发生了异常。
2. 在前面的代码中用了 System.exit() 退出程序。
3. 程序所在的线程死亡。
4. 关闭 CPU。

1.0.1.3 为什么要使用通配符?上界通配符和下界通配符如何理解和注意要点?什么是无界通配符?

为什么要使用通配符
通配符的设计存在一定的场景,例如在使用泛型后,首先声明了一个 Animal 的类,而后声明了一个继承 Animal 类的 Cat 类,显然 Cat 类是 Animal 类的子类,但是 List<Cat> 却不是 List<Animal> 的子类型,而在程序中往往需要表达这样的逻辑关系。为了解决这种类似的场景,在泛型的参数类型的基础上新增了通配符的用法。

<? extends T> 上界通配符
上界通配符顾名思义,<? extends T> 表示的是类型的上界【包含自身】,因此通配的参数化类型可能是 T 或 T 的子类。正因为无法确定具体的类型是什么,add 方法受限(可以添加 null,因为 null 表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个 add() 操作会受限,原因在于 List<Animal> 和 List<Cat> 是 List<? extends Animal> 的子类型。

<? super T> 下界通配符
下界通配符 <? super T> 表示的是参数化类型是 T 的超类型(包含自身),层层至上,直至 Object,编译器无从判断 get() 返回的对象的类型是什么,因此 get() 方法受限。但是可以进行 add() 方法,add() 方法可以添加 T 类型和 T 类型的子类型,如第二个例子中首先添加了一个 Cat 类型对象,然后添加了两个 Cat 子类类型的对象,这种方法是可行的,但是如果添加一个 Animal 类型的对象,显然将继承的关系弄反了,是不可行的。

<?> 无界通配符

任意类型,如果没有明确,那么就是 Object 以及任意的 Java 类了
无界通配符用 <?> 表示,? 代表了任何的一种类型,能代表任何一种类型的只有 null(Object 本身也算是一种类型,但却不能代表任何一种类型,所以 List<Object> 和 List<null> 的含义是不同的,前者类型是 Object,也就是继承树的最上层,而后者的类型完全是未知的)。
技术博客大总结

1.0.1.4 什么是泛型擦除,能否通过开发中实际案例说下?如何获取泛型的具体的类型【反射】?

开发中的泛型擦除案例
泛型是提供给 javac 编译器使用的,限定集合的输入类型,编译器编译带类型说明的集合时会去掉“类型”信息。
public class GenericTest {
public static void main(String[] args) {
new GenericTest().testType();
}
public void testType(){
ArrayList<Integer> collection1 = new ArrayList<Integer>();
ArrayList<String> collection2= new ArrayList<String>();
System.out.println(collection1.getClass()==collection2.getClass());
// 两者 class 类型一样, 即字节码一致
System.out.println(collection2.getClass().getName());
//class 均为 java.util.ArrayList, 并无实际类型参数信息
}

// 输出结果
//true
//java.util.ArrayList
}

如何获取泛型的具体的类型?

使用反射可跳过编译器,往某个泛型集合加入其它类型数据。
只有引用类型才能作为泛型方法的实际参数,具体案例如下所示

public class GenericTest {
public static void main(String[] args) {
swap(new String[]{“111″,”222”},0,1);// 编译通过

//swap(new int[]{1,2},0,1);
// 编译不通过, 因为 int 不是引用类型

swap(new Integer[]{1,2},0,1);// 编译通过
}

/* 交换数组 a 的第 i 个和第 j 个元素 */
public static <T> void swap(T[]a,int i,int j){
T temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
但注意基本类型有时可以作为实参,因为有自动装箱和拆箱。下面例子 (编译通过了):
public class GenericTest {
public static void main(String[] args) {
new GenericTest().testType();
int a = biggerOne(3,5);
//int 和 double, 取交为 Number
Number b = biggerOne(3,5.5);
//String 和 int 取交为 Object
Object c = biggerOne(“1”,2);
}
// 从 x,y 中返回 y
public static <T> T biggerOne(T x,T y){
return y;
}
}

同时,该例还表明,当实参不一致时,T 取交集,即第一个共同的父类。

另外,如果用 Number b = biggerOne(3,5.5); 改为 String c = biggerOne(3,5.5); 则编译报错:

Error:(17, 29) java: 不兼容的类型: 推断类型不符合上限
推断: java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>>
上限: java.lang.String,java.lang.Object

1.0.1.5 如何验证 int 类型是否线程安全?那些类型是线程安全的?举一个线程安全的例子【AtomicInteger】?

如何验证 int 类型是否线程安全

200 个线程,每个线程对共享变量 count 进行 50 次 ++ 操作
int 作为基本类型,直接存储在内存栈,且对其进行 +,- 操作以及 ++,–操作都不是原子操作,都有可能被其他线程抢断,所以不是线程安全。int 用于单线程变量存取,开销小,速度快
技术博客大总结

int count = 0;
private void startThread() {
for (int i = 0;i < 200; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 50; k++){
count++;
}
}
}).start();
}
// 休眠 10 秒,以确保线程都已启动
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
Log.e(“ 打印日志 —-“,count+””);
}
}

// 期望输出 10000,最后输出的是 9818
// 注意:打印日志 —-: 9818

那些类型是线程安全的
Java 自带的线程安全的基本类型包括:AtomicInteger, AtomicLong, AtomicBoolean, AtomicIntegerArray,AtomicLongArray 等

AtomicInteger 线程安全版

AtomicInteger 类中有有一个变量 valueOffset,用来描述 AtomicInteger 类中 value 的内存位置。
当需要变量的值改变的时候,先通过 get()得到 valueOffset 位置的值,也即当前 value 的值. 给该值进行增加,并赋给 next
compareAndSet()比较之前取到的 value 的值当前有没有改变,若没有改变的话,就将 next 的值赋给 value,倘若和之前的值相比的话发生变化的话,则重新一次循环,直到存取成功,通过这样的方式能够保证该变量是线程安全的
value 使用了 volatile 关键字,使得多个线程可以共享变量,使用 volatile 将使得 VM 优化失去作用,在线程数特别大时,效率会较低。

private static AtomicInteger atomicInteger = new AtomicInteger(1);
static Integer count1 = Integer.valueOf(0);
private void startThread1() {
for (int i = 0;i < 200; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 50; k++){
// getAndIncrement: 先获得值,再自增 1,返回值为自增前的值
count1 = atomicInteger.getAndIncrement();
}
}
}).start();
}
// 休眠 10 秒,以确保线程都已启动
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
Log.e(“ 打印日志 —-“,count1+””);
}
}

// 期望输出 10000,最后输出的是 10000
// 注意:打印日志 —-: 10000

//AtomicInteger 使用了 volatile 关键字进行修饰,使得该类可以满足线程安全。
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}

1.0.1.6 Java 序列话中如果有些字段不想进行序列化怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

1.0.1.8 原始数据类型和引用类型局限性?为何要引用基本数据包装类?

原始数据类型和引用类型局限性

原始数据类型和 Java 泛型并不能配合使用
Java 的泛型某种程度上可以算作伪泛型,它完全是一种编译期的技巧,Java 编译期会自动将类型转换为对应的特定类型,这就决定了使用泛型,必须保证相应类型可以转换为 Object。

为何要引用基本数据包装类

就比如,我们使用泛型,需要用到基本数据类型的包装类。
Java 的对象都是引用类型,如果是一个原始数据类型数组,它在内存里是一段连续的内存,而对象数组则不然,数据存储的是引用,对象往往是分散地存储在堆的不同位置。这种设计虽然带来了极大灵活性,但是也导致了数据操作的低效,尤其是无法充分利用现代 CPU 缓存机制。
Java 为对象内建了各种多态、线程安全等方面的支持,但这不是所有场合的需求,尤其是数据处理重要性日益提高,更加高密度的值类型是非常现实的需求。

1.0.1.9 new Integer(123) 与 Integer.valueOf(123) 有何区别,请从底层实现分析两者区别?

new Integer(123) 与 Integer.valueOf(123) 的区别在于:

new Integer(123) 每次都会新建一个对象;
Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。

Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true

valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

在 Java 8 中,Integer 缓存池的大小默认为 -128~127。
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty(“java.lang.Integer.IntegerCache.high”);
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE – (-low) -1);
} catch(NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high – low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true

基本类型对应的缓冲池如下:

boolean values true and false
all byte values
short values between -128 and 127
int values between -128 and 127
char in the range u0000 to u007F

在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。

其他介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

正文完
 0