JAVA问答
- [Q1 lock 和 synchronized 有什么区别](#Q1 lock 和 synchronized 有什么区别)
-
-
- [Java 中 Lock 与 Synchronized 的对比](#Java 中 Lock 与 Synchronized 的对比)
-
- [1. 功能角度](#1. 功能角度)
- [2. 特性角度](#2. 特性角度)
- [3. 性能角度](#3. 性能角度)
-
- [Q2 多态性如何实现?](#Q2 多态性如何实现?)
-
-
- [1. 运行时多态(动态绑定)------ 核心机制](#1. 运行时多态(动态绑定)—— 核心机制)
- [2. 编译时多态(静态绑定)](#2. 编译时多态(静态绑定))
- [3. 总结:多态的实现方式对比](#3. 总结:多态的实现方式对比)
- [4. 为什么使用多态?](#4. 为什么使用多态?)
-
Q1 lock 和 synchronized 有什么区别
Java 中 Lock 与 Synchronized 的对比
1. 功能角度
- 共同点:Lock 和 synchronized 都是 Java 中用来解决线程安全问题的工具。
2. 特性角度
- 类型与实现 :
- synchronized:是 Java 中的同步关键字。
- Lock:是 JUC 包里面提供的一个接口,有很多实现类(如 ReentrantLock 重入锁)。
- 锁的粒度与作用范围 :
- synchronized:
- 粒度 :可通过修饰方法 或代码块来控制。
- 范围:取决于加锁对象的生命周期(静态/类对象为全局锁,普通实例对象取决于实例生命周期)。
- Lock:
- 粒度:通过 lock() 和 unlock() 方法包裹代码来决定。
- 范围:取决于 Lock 实例的生命周期。
- synchronized:
- 灵活性 :
- Lock 更灵活,可自主决定加锁和释放锁的时间。
- Lock 提供了 tryLock() 方法,支持非阻塞竞争锁(返回 true/false),而 synchronized 无法实现此功能。
- 锁释放机制 :
- synchronized:释放是被动的(同步代码块执行结束或出现异常时)。
- Lock:需手动释放(调用 unlock() 方法)。
- 公平锁机制 :
- Lock:支持公平锁(排队,不插队)和非公平锁(尝试插队)。
- synchronized:只提供了一种非公平锁的实现。
3. 性能角度
- 总体表现:两者在性能方面相差不大,但实现机制不同。
- synchronized:引入了偏向锁、轻量级锁、重量级锁以及锁升级机制来实现优化。
- Lock:用到了自旋锁的方式去实现性能优化。
Q2 多态性如何实现?
在 Java 中,多态性(Polymorphism)是面向对象编程(OOP)的三大核心特性之一,其核心思想是**"同一个行为,具有多种不同的实现"**。通俗地说,就是 ** "父类引用指向子类对象" ** ,并在运行时根据实际对象的类型来决定调用哪个方法。
在 Java 中,多态的实现主要依赖于运行时多态(动态绑定) ,同时也包含编译时多态(静态绑定)。以下我将为你详细拆解这两种实现方式,重点是运行时多态的底层原理。
1. 运行时多态(动态绑定)------ 核心机制
这是 Java 多态最常用、也是最核心的实现方式,它允许程序在运行时才确定具体调用哪个类的方法。
实现条件(缺一不可)
要实现运行时多态,必须同时满足以下三个条件:
- 继承关系 :必须存在类的继承(
extends)或接口的实现(implements)。 - 方法重写 :子类必须重写(
@Override)父类的方法。 - 向上转型 :必须使用父类的引用 指向子类的对象 (例如:
Parent p = new Child();)。
代码示例
java
// 父类
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
// 子类 Dog
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪"); // 重写父类方法
}
}
// 子类 Cat
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵"); // 重写父类方法
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 向上转型:父类引用指向子类对象
Animal animal2 = new Cat();
animal1.makeSound(); // 输出:汪汪
animal2.makeSound(); // 输出:喵喵
}
}
在上面的代码中,虽然 animal1 和 animal2 的引用类型都是 Animal,但调用 makeSound() 方法时,实际执行的是它们各自子类中重写后的方法。
底层实现原理:动态绑定与方法表
Java 是如何做到这一点的?这依赖于 JVM 的动态绑定(Dynamic Binding)机制和方法表(Method Table):
- 编译期检查 :在编译阶段,编译器只看引用变量的类型(左边)。它会检查
Animal类中是否有makeSound()方法。如果有,编译通过;如果没有,报错。此时并不确定具体执行哪个类的代码。 - 运行期决议 :在程序运行时,JVM 会通过对象的实际类型(右边,即
new出来的对象)去查找该类的方法表。 - 虚方法调用 :JVM 发现
makeSound()是一个虚方法(非static、非private、非final),它会去实际对象(如Dog)的方法表中查找该方法的直接引用。 - 调用执行 :JVM 调用的是实际对象(
Dog或Cat)中重写后的方法。
注意 :这种机制也被称为"晚绑定"。如果方法是
static、private或final的,它们不参与多态,因为它们在编译期就已经确定了调用版本(静态绑定)。
2. 编译时多态(静态绑定)
虽然通常提到多态指的都是运行时多态,但 Java 中也存在编译时多态。
- 实现方式 :方法重载(Overloading)。
- 原理 :在同一个类中,允许存在多个同名方法,只要它们的参数列表(参数类型、个数、顺序)不同。编译器在编译代码时,就会根据你传入的参数类型和数量,直接确定调用哪一个具体的方法。
- 特点:发生在编译期,与继承无关。
3. 总结:多态的实现方式对比
| 特性 | 编译时多态 (静态) | 运行时多态 (动态) |
|---|---|---|
| 实现机制 | 方法重载 (Overloading) | 方法重写 (Overriding) + 向上转型 |
| 绑定时机 | 编译期 (Compiler Time) | 运行期 (Runtime) |
| 核心依据 | 方法的参数列表 | 对象的实际类型 (JVM 动态查找) |
| 性能开销 | 无运行时开销 | 有轻微的查表开销 (方法表查找) |
| 灵活性 | 较低 | 高 (支持"开闭原则",易于扩展) |
4. 为什么使用多态?
- 消除类型之间的耦合关系:调用者只需要关注父类或接口定义的行为,而不需要知道具体的子类是谁。
- 可扩展性强 :如果需要增加新功能(例如新增一个
Bird类继承Animal),只需要让新类继承父类并重写方法,无需修改已有的调用代码(如Test类中的逻辑)。
简单总结:Java 多态的实现就是**"继承做基础,重写做前提,父类引用指向子类对象,JVM 运行时查表调用"**。