Skip to content

Commit

Permalink
🐞 fix: 更正一些内容 & 🌟 feat: 增加2023阿里、小红书面试题
Browse files Browse the repository at this point in the history
1. 🐞 fix: 更正一些内容
2. 🌟 feat: 增加2023阿里、小红书面试题
  • Loading branch information
zhiyu1998 committed Jun 14, 2023
1 parent e2ff96b commit a3d4b4d
Show file tree
Hide file tree
Showing 10 changed files with 659 additions and 109 deletions.
115 changes: 107 additions & 8 deletions src/Java/eightpart/concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,121 @@ category:

> 精简版
**进程是系统资源分配和调度的基本单位**,它能并发执行较高系统资源的利用率.
**进程**是系统资源**分配和调度的一个独立单位**,每个进程有自己的内存空间和系统资源。一个进程可以包含多个线程,这些线程共享进程的堆和元空间(JDK1.8之后的方法区),但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。线程作为进程内部的并发执行单位,切换代价小,但可能会相互影响。

**线程****比进程更小**的能独立运行的基本单位,创建、销毁、切换成本要小于进程,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。
**线程**是程序并发执行的**最小单位**,因为创建、销毁、切换的成本相较于进程更小,所以能够更好地提高程序的并发性和系统资源的利用率。

> 另外在这里还可以再扩展一下:**协程**和Java 19的**虚拟线程**
> 1. **协程(Coroutines)** 是一种比线程更轻量级的存在,它们的特点是可以在用户态进行调度,而无需涉及到系统调度,因此创建、切换的成本更低。不过,协程并不是由操作系统提供的概念,而是由特定的编程语言或者库提供。协程的一个重要特性是可以通过 yield 和 resume 操作进行手动调度,允许在函数内部保存状态,从而实现非常轻量级的任务切换和并发。
> 2. **Java 19引入了虚拟线程(Virtual Threads)** 的概念,也被称为轻量级线程或者**纤程(fibers)**。虚拟线程是为了解决Java并发编程中的一些问题,比如高并发时线程资源的消耗,线程切换导致的性能开销等。相比于常规线程,虚拟线程的创建和切换开销更小,这是因为虚拟线程的调度不是由操作系统来完成,而是在用户态由Java运行时系统来完成。虚拟线程使得开发者可以在不担心性能和资源问题的前提下,创建大量的线程来提高程序的并发性能。
> - https://docs.oracle.com/en/java/javase/19/core/virtual-threads.html 官方虚拟线程介绍
> - https://www.javacodegeeks.com/2023/03/intro-to-java-virtual-threads.html 虚拟线程Java简介
> - https://spring.io/blog/2022/10/11/embracing-virtual-threads 拥抱虚拟线程
> - https://stackoverflow.com/questions/796217/what-is-the-difference-between-a-thread-and-a-fiber 线程和fibers有什么区别?
### Java线程的创建方式

**四种**方式可以用来创建线程:

- 继承**Thread**
- 实现**Runnable**接口
- **Callable****FutureTask**创建线程:为了解决异步执行的结果问题,Java语言在1.5版本之后提供了一种新的多线程创建方法:通过Callable接口和FutureTask类相结合创建线程。
- **线程池**创建线程
- 使用Executors创建线程池
1. 继承**Thread**
```java
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("This is a thread created by extending Thread class.");
}

public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
```
2. 实现**Runnable**接口
```java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("This is a thread created by implementing Runnable interface.");
}

public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
```
3. **Callable****FutureTask**创建线程:为了解决异步执行的结果问题,Java语言在1.5版本之后提供了一种新的多线程创建方法:通过Callable接口和FutureTask类相结合创建线程。
```java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String> {
@Override
public String call() {
return "This is a thread created by using Callable and FutureTask.";
}

public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();

try {
System.out.println(futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
```
4. **线程池**创建线程,使用Executors创建线程池
```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);

Runnable task1 = () -> System.out.println("Task 1 is executed by a thread in the thread pool.");
Runnable task2 = () -> System.out.println("Task 2 is executed by a thread in the thread pool.");
Runnable task3 = () -> System.out.println("Task 3 is executed by a thread in the thread pool.");

executorService.execute(task1);
executorService.execute(task2);
executorService.execute(task3);

executorService.shutdown();
}
}
```

实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。

> 扩展:Java19如何创建一个虚拟线程
```java
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class FiberExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
System.out.println("Hello from fiber!");
}).get();
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
}
}
```

### JMM 是什么?

网上的概念鱼龙混杂,笔者直接摘录部分《Java高并发核心编程》和《Java并发编程的艺术》中的介绍:
Expand Down Expand Up @@ -148,7 +247,7 @@ JMM规定:
happens-before 这个概念最早诞生于 Leslie Lamport 于 1978 年发表的论文[《Time,Clocks and the Ordering of Events in a Distributed System》](https://lamport.azurewebsites.net/pubs/time-clocks.pdf)。在这篇论文中,Leslie Lamport 提出了逻辑时钟open in new window的概念,这也成了第一个逻辑时钟算法 。在分布式环境中,通过一系列规则来定义逻辑时钟的变化,从而能通过逻辑时钟来对分布式系统中的事件的先后顺序进行判断。逻辑时钟并不度量时间本身,仅区分事件发生的前后顺序,其本质就是定义了一种 happens-before 关系。

上面提到的 happens-before 这个概念诞生的背景并不是重点,简单了解即可。

JSR 133 引入了 happens-before 这个概念来描述两个操作之间的内存可见性。

为什么需要 happens-before 原则? happens-before 原则的诞生是为了程序员和编译器、处理器之间的平衡。程序员追求的是易于理解和编程的强内存模型,遵守既定规则编码即可。编译器和处理器追求的是较少约束的弱内存模型,让它们尽己所能地去优化性能,让性能最大化。happens-before 原则的设计思想其实非常简单:
Expand Down
74 changes: 60 additions & 14 deletions src/Java/eightpart/foundation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,68 @@ category:
- **Optional 类** − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- **Nashorn, JavaScript 引擎** − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

### 重载和重写的区别?
### 重载(Overloading)和重写(Overriding)的区别?
1. 重载(Overloading)
重载是指在同一个类中,方法名相同但参数列表不同(参数的顺序、类型、数量不同)的多个方法。重载允许我们根据不同的输入参数对同一个方法名进行多次定义。
```java
class Printer {
void print(int number) {
System.out.println("打印整数: " + number);
}

void print(double number) {
System.out.println("打印浮点数: " + number);
}

void print(String text) {
System.out.println("打印文本: " + text);
}
}
```

发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问
修饰符可以不同。
下面是《Java 核心技术》对重载这个概念的介绍:
2. 重写(Overriding)

重写是指子类继承父类时,子类中重新定义了父类中的方法。重写的方法必须与父类方法具有相同的方法名、参数列表和返回类型。重写的目的是为了让子类可以提供与父类相同的方法名,但具有不同的实现,从而实现多态。
```java
class Shape {
double area() {
return 0;
}
}

class Circle extends Shape {
double radius;

综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
重写:
重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
Circle(double radius) {
this.radius = radius;
}

@Override
double area() {
return Math.PI * radius * radius;
}
}

class Rectangle extends Shape {
double width, height;

Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

@Override
double area() {
return width * height;
}
}
```

1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围
大于等于父类。
2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰
的方法能够被再次声明。
3. 构造方法无法被重写
综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变
在面试中介绍这两个概念时,可以按以下步骤进行:
1. 首先定义重载和重写,强调它们之间的区别。
2. 举例说明重载和重写的应用场景。
3. 提及重载和重写在代码实现上的规则(方法名、参数列表、返回类型)。
4. 可以简要提及重载和重写在实现多态和代码可读性方面的作用。

![image-20220617164632798](./personal_images/image-20220617164632798.png)

Expand Down Expand Up @@ -144,7 +190,7 @@ class Outter {

#### static

**static 关键字主要有以下四种使用场景:**
static 关键字主要有以下四种使用场景:

1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
Expand Down
Loading

0 comments on commit a3d4b4d

Please sign in to comment.