Skip to content

Commit

Permalink
🐞 fix: 更正部分知识,增加一点新内容
Browse files Browse the repository at this point in the history
1. 更正部分知识内容
2. 增加大厂面试题、部分零散知识
  • Loading branch information
zhiyu1998 committed Jul 6, 2023
1 parent fd4e78a commit b883e49
Show file tree
Hide file tree
Showing 54 changed files with 1,929 additions and 423 deletions.
78 changes: 66 additions & 12 deletions src/Java/eightpart/concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,22 +573,40 @@ i++;j++;

这个时候可以使用 synchronized 进行加锁,有没有其他办法呢?有,将多个变量操作合成一个变量操作。从 JDK1.5 开始提供了 `AtomicReference` 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

### 什么是上下文切换?

多线程编程中一般线程的个数都大于CPU核心的个数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来说就是:**当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换。
### 说说 sleep() 方法和 wait() 方法区别和共同点?

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的CPU时间,事实上,可能是操作系统中时间消耗最大的操作。
(最主要的区别)锁持有:

Linux相比与其他操作系统(包括其他类Unix系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
- `sleep()` 方法:在睡眠期间,线程并没有释放所持有的锁。
- `wait()` 方法:调用 `wait()` 方法后,线程会释放所持有的锁,允许其他线程使用同步代码块。



线程暂停执行:

- 两者都可以使线程暂停执行,但是他们通常应用在不同的上下文。



使用场景:

- `wait()` 方法:常被用于线程间的交互/通信,特别是在生产者消费者模式中,用于告知某个正在等待的线程,现在可以开始工作了。
- `sleep()` 方法:常被用于暂停执行,比如模拟 IO 操作、复杂计算或者其他任何可能需要花费时间的操作后,暂停一段时间再继续执行。



线程苏醒:

- `wait()` 方法:线程不会自动苏醒,需要其他线程调用 `notify()``notifyAll()` 方法来唤醒等待的线程。然而,可以指定一个超时参数(`wait(long timeout)`),在超时期满后线程会自动苏醒。
- `sleep()` 方法:不需要被其他线程显式唤醒。当指定的睡眠时间到了,线程会自动苏醒。



所属类别:Java中,`sleep()` 方法是 `Thread` 类的静态方法,这意味着,它会对当前正在执行的线程进行操作。而 `wait()` 方法是从 `Object` 类继承的,每个对象都有 `wait()` 方法,它需要在同步块或同步方法中被调用,意味着调用 `wait()` 的线程必须拥有该对象的锁。

### 说说 sleep() 方法和 wait() 方法区别和共同点?

- 两者最主要的区别在于:**`sleep()` 方法没有释放锁,而 `wait()` 方法释放了锁**
- 两者都可以暂停线程的执行。
- `wait()` 通常被用于线程间交互/通信,`sleep() `通常被用于暂停执行。
- `wait()` 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 `notify() `或者 `notifyAll()` 方法。`sleep() `方法执行完成后,线程会自动苏醒。或者可以使用 `wait(long timeout)` 超时后线程会自动苏醒。

### 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

Expand All @@ -598,6 +616,8 @@ new 一个 Thread,线程进入了新建状态。调用 `start()`方法,会

**总结: 调用 `start()` 方法方可启动线程并使线程进入就绪状态,直接执行 `run()` 方法的话不会以多线程的方式执行。**



### synchronized汇总

#### 说一说自己对于 synchronized 关键字的了解
Expand All @@ -614,6 +634,8 @@ new 一个 Thread,线程进入了新建状态。调用 `start()`方法,会
>
> 🙋‍♂️**synchronized**关键字用于为Java对象、方法、代码块提供线程安全的操作。synchronized属于独占式的悲观锁,同时属于可重入锁。在使用synchronized修饰对象时,同一时刻只能有一个线程对该对象进行访问;在synchronized修饰方法、代码块时,同一时刻只能有一个线程执行该方法体或代码块,其他线程只有等待当前线程执行完毕并释放锁资源后才能访问该对象或执行同步代码块。Java中的每个对象都有个monitor对象,加锁就是在竞争monitor(监视器锁)对象。对代码块加锁是通过在前后分别加上monitorenter和monitorexit指令实现的,对方法是否加锁是通过一个标记位来判断的。


#### 讲一下 synchronized 关键字的底层原理

##### synchronized 同步语句块的情况
Expand Down Expand Up @@ -815,9 +837,41 @@ public class Thread implements Runnable {

> 注:`ThreadLocalMap``ThreadLocal`的静态内部类。
【扩展】 **ThreadLocal为什么会导致内存泄露**(简化版):
**ThreadLocal的key为什么设计成弱引用**

弱引用,与强引用不同的是,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收被弱引用关联的对象。在Java中,提供了WeakReference类来实现弱引用。

```java
static class ThreadLocalMap {

/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
```

那么,为什么ThreadLocal需要使用弱引用来作为key呢?这主要是因为ThreadLocal有一个特性,即每个Thread都会持有一个ThreadLocalMap,而ThreadLocalMap中存放着ThreadLocal作为key和对应用户数据作为value的键值对。在这种设计下,如果ThreadLocal的key是强引用,那么只要Thread还在,那么ThreadLocal就不会被垃圾回收,即使你已经没有任何强引用指向ThreadLocal对象。这就可能导致你的用户数据无法被垃圾回收,从而产生内存泄漏。

但是如果ThreadLocal的key是弱引用,一旦你的代码里没有强引用指向ThreadLocal对象,ThreadLocal就可能被垃圾回收,从而使得ThreadLocalMap中的Entry的key变为null,这时候,ThreadLocalMap的get、set、remove等方法都会清理掉这些key为null的Entry,从而使得用户数据得以被垃圾回收。

总的来说,ThreadLocal的key使用弱引用是为了更好的控制内存的使用,防止因为使用ThreadLocal而导致的内存泄漏问题。

具体详细看Guide:[ThreadLocal的内存泄露](https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#threadlocal-%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E9%97%AE%E9%A2%98)
**ThreadLocal为什么会导致内存泄露**(简化版):

`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

Expand Down
Loading

0 comments on commit b883e49

Please sign in to comment.