# 春招第一笔

## 题目分布
共15个选择题、6个问答、3个编程

![image-20240310135821403](https://cs-wlei224.obs.cn-south-1.myhuaweicloud.com/blog-imgs/202403101358531.png)

## 题目

### 问答Q1：

首先，这三个特性都是线程安全的三个提现。
其次，原子性就是线程之间互斥访问，同一时间，只能有一个线程在进行操作，比如锁、synchronized、CAS算法；
可见性就是如果同一内存中一个线程做出了修改操作，那么对于其他线程来说也都是同步可见的；
有序性就是Hapens-before算法，即我们不需要关注顺序，因为 JVM 内部会进行顺序打乱，所以无需关注顺序。

### 问答Q2：

BIO(Blocking IO)：同步阻塞IO
NIO(NonBlockingIO)：同步不阻塞IO
AIO(asynchronousIO)：异步不阻塞IO

BIO就相当于一个链接一个线程，发起请求会一直阻塞，可以通过连接池来改善；
NIO就相当于多个连接复用一个线程，一个请求一个线程（我的理解是BIO+连接池）；
AIO就是一个有效请求一个线程，IO发起请求，立马响应，操作结束后，异步回调通知结果。

### 问答Q3：

当空间不足的时候会触发。
1，旧生代空间不足
2，Minor GC 空间不足，低于旧生代空间内存
3，perm gen空间存放 class 信息满

### 问答Q4：

显而易见，此表中并没有合适的字段作为索引。因为性别、省份区分度不大，不适合索引，然而身份证号长度太长，作为索引效率不高，没有必要。

我的索引优化思路：
新增一个字段，比如可以截取身份证后几位，或者通过一些哈希算法对idcard进行运算，得到一个新字段，然后把这个不易重复的字段作为唯一索引。

### 问答Q5：

隔离级别：
ISOLATION_DEFAULT默认隔离级别
ISOLATION_READ_UNCOMMITED修改时可读（出现幻读）
ISOLATION_READ_COMMITED修改后才可读（避免幻读）
ISOLATION_REPEATABLE_READ防止脏读
ISOLATION_SERIALIZABLE(完美隔离级别)

事务传播行为：
默认为propagation.REQUIRED，如果当前存在事务，就加入，不存在就创建

### 问答Q6：

自动装箱与自动拆箱是 Java 的语法糖之一。
简单来说，自动装箱就是将 Java 的基本数据类型转换为对应的对象，比如 int 转换为 Integer，float 转换为 Float；反之，自动拆箱就是讲对应类型的对象重新转换为基本类型。
其中，通过我的研究，发现反编译之后的代码，自动装箱的原理是这样的：
int i = 5；
Integer n = Integer.valueOf(i);
自动拆箱原理：
Integer i = 5；
int n = i.intValue();



### 编程Q1：

#### 几种常见的单例模式:

```java
// 懒汉模式————先不创建单例，用的时候再创建
public class Singleton {
  private static Singleton instance;
  private Singleton() {}
    
  public static synchronized Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}      

// 饿汉模式————立即创建单例，因此线程安全
public class Singleton {
  private static Singleton instance = new Singleton();
  private Singleton() {}
    
  public static Singleton getInstance() {
    return instance;
  }
}      

public class Singleton {
  private static Singleton instance = null;
  static {
      instance = new Singleton();
  }
  private Singleton() {}
    
  public static Singleton getInstance() {
    return this.instance;
  }
}  

// 静态内部类
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton() {}
    
    public static final Singleton getInstance() {
        return SingletonHolder.INSTAMCE;
    } 
}

// 枚举
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
        ···
    }
}
```

#### 简单实现一下单例模式的调用：

```java 
package eqianbao;

/**
 * 模拟一下单例模式的调用（以懒汉模式为例）
 * @Author <a href="https://github.com/wl2o2o">程序员CSGUIDER</a>
 * @From <a href="https://wl2o2o.github.io">CSGUIDER博客</a>
 * @CreateTime 2024/3/10
 */

public class SingletonLazy {

    private static SingletonLazy instance;

    private SingletonLazy(){}

    // 注意看这里用了同步锁
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    public void say() {
        System.out.println("i'm lazyPeople!");
    }

    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
        instance.say();
    }
}
```

> 懒汉单例模式其实是线程不安全的，因为懒汉模式没有事先创建单例，所以我在创建单例的时候加上了同步锁，通过同步锁的同步互斥访问来达到线程安全的目的。更常用的方法还有双重检查锁（Double-Check-Lock）,代码如下:

```java
public class SingletonLazy {

    private static SingletonLazy instance;

    private SingletonLazy(){}

    public static SingletonLazy getInstance() {
        // 双重校验锁
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}
```

> 如果我放任不管呢？是如何线程不安全的？诶我就不加锁，就是玩！就是玩！就是玩！show me your code！

```java
public class SingletonLazy {

    private static SingletonLazy instance;

    private SingletonLazy(){}

    // 注意看!这里没有！！！同步锁！
    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    public void say() {
        System.out.println("i'm lazyPeople!就是玩!");
    }

    public static void main(String[] args) {
        
        SingletonLazy instance = SingletonLazy.getInstance();
        instance.say();
    }
}
```

> 最终经过多种情况尝试，发现不加同步锁的懒汉单例模式并没有创建多个单例，可能因为现代 JVM 的内存模型、内部优化 和 CPU 之间的通信方式，通过短时间循环和简单线程池模拟并不能体现出创建了多个单例，线程调度的不确定性仍然存在。
>
> 所以，我们需要了解这句话：非线程安全单例模式**在某些情况下可能**导致多个实例。因此总结一下，**没有同步锁，不一定不安全；有同步锁，一定安全。**

下面的代码是我想复现一下懒汉单例模式如果不加同步锁的情况下，多线程会创建多个`instance`，这样就违背单例的概念了，但是模拟代码均未实现创建多例的情况，具体原因如上述描述↑

#### 模拟实现不加同步锁，会创建多例的情况代码：

```java
// 创建多个线程   
public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            UnsafeLazySingleton instance = UnsafeLazySingleton.getInstance();
            instance.say();
            System.out.println(instance.hashCode());
        }).start();
    }
}

// 增加循环次数，并让线程休息一会，模拟更加真实的场景
public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 900; i++) {
        new Thread(() -> {
            UnsafeLazySingleton instance = UnsafeLazySingleton.getInstance();
            instance.say();
            System.out.println(instance.hashCode());
        }).start();
        Thread.sleep(1000);
    }
}

// 上述发现进程执行的太快，睡一睡发现还是获取的单例哈希值，继续优化，加上了 thread.join()
public static void main(String[] args) throws InterruptedException {
    final int LOOP_COUNT = 100; // 设置循环次数

    for (int i = 0; i < LOOP_COUNT; i++) {
        Thread thread = new Thread(() -> {
            UnsafeLazySingleton instance = UnsafeLazySingleton.getInstance();
            instance.say();
            System.out.println("Thread ID: " + Thread.currentThread().getId());
            System.out.println("Hash Code: " + instance.hashCode());
        });
        thread.start();
        thread.join(); // 等待每个线程执行完毕，确保输出顺序清晰
    }
}

// 不行还是不行，使用线程池试一下
public static void main(String[] args) throws InterruptedException, ExecutionException {
    final int LOOP_COUNT = 1000; // 设置循环次数
    ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池

    // Future<Integer>：executor.submit()通过 lambda 表达式提交 instance 的哈希值，存放于 Future<Integer> 中
    List<Future<Integer>> futures = new ArrayList<>();
    Random random = new Random();

    for (int i = 0; i < LOOP_COUNT; i++) {
        Future<Integer> future = executor.submit(() -> {
            UnsafeLazySingleton instance = UnsafeLazySingleton.getInstance();
            instance.say();
            return instance.hashCode();
        });
        futures.add(future);
        // 在这里添加一些随机延时以增加并发冲突的可能性
        Thread.sleep(random.nextInt(10)); // 随机等待时间（单位：毫秒）
    }
    
	// 遍历取出哈希值
    for (Future<Integer> future : futures) {
        System.out.println("Hash Code: " + future.get());
    }
	// 关闭线程池
    executor.shutdown();
}
```
> 等等，绝对不可能，今天的我发现了昨天很傻的我，我发现了问题所在！用`STAR`原则来分析一下：
- Situation（情景）：懒汉单例模式不是线程安全的
- Task（任务）：模拟实现（证明）线程不安全
- Action（行动）：通过拆分分析代码，只有当多个线程同时发现当前实例为空时，才会`new`新的实例，因此增加线程冲突率，减少循环次数（多次循环会堵塞）
- Result（结果）：去掉线程间睡眠时间、减少为两个循环，以此增加碰撞率，提高了创建多例可能性

```java
package singletondesignpattern;

/**
 * @Author <a href="https://github.com/wl2o2o">程序员CSGUIDER</a>
 * @From <a href="https://wl2o2o.github.io">CSGUIDER博客</a>
 * @CreateTime 2024/3/12
 */

public class NonThreadSafeSingleton {
    private static NonThreadSafeSingleton instance;

    private NonThreadSafeSingleton() {}

    // 注意：这里没有加锁，所以是线程不安全的
    public static NonThreadSafeSingleton getInstance() {
        if (instance == null) {
            // 在多线程环境下，两个线程可能同时发现 instance 为 null，并各自创建一个实例
            instance = new NonThreadSafeSingleton();
        }
        return instance;
    }

    @Override
    public String toString() {
        return "NonThreadSafeSingleton@" + Integer.toHexString(hashCode());
    }

    public static void main(String[] args) {
        // 创建两个并发线程来获取单例
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                System.out.println("Thread " + Thread.currentThread().getName() + ": " + NonThreadSafeSingleton.getInstance());
            }).start();
        }
    }
}
```
模拟成功截图：
![e签宝24春招笔试-2024-03-13-01-23-10](https://cdn.jsdelivr.net/gh/wl2o2o/blogCdn/img/e签宝24春招笔试-2024-03-13-01-23-10.png)

#### 模拟实现加同步锁，一定安全代码：
按照上述方法加上`synchronized`同步锁关键字，或者使用双重校验锁，也或者可以使用`ConcurrentHashMap`，都可以解决线程安全问题。

以`ConcurrentHashMap`为例：

```java
public class ConcurrentSingleton {
    private static ConcurrentHashMap<String, ConcurrentSingleton> map = new ConcurrentHashMap<>();
    private static final String KEY = "key";
    private ConcurrentSingleton() {}
    public static ConcurrentSingleton getInstance() {
        return map.computeIfAbsent(KEY, k -> {
            ConcurrentSingleton instance = new ConcurrentSingleton();
            return instance;
        });
    }
    @Override
    public String toString() {
        return "ConcurrentSingleton@" + Integer.toHexString(hashCode());
    }
    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                System.out.println("Thread " + Thread.currentThread().getName() + ": " + ConcurrentSingleton.getInstance());
            }).start();
        }
    }
}
```

实现线程安全的单例模式还有很多种方式，比如：
- 使用饿汉式单例模式，
- 使用静态内部类，
- 使用双重校验锁，
- 使用枚举类，
- 使用`synchronized`关键字，
- 使用`ConcurrentHashMap`，
- 使用`ThreadLocal`，
- 使用`volatile`关键字，
- 使用`AtomicReference`，
- 使用`Unsafe`，
- 使用`JUC`的`AtomicReference`，
- 使用`JUC`的`AtomicStampedReference`，
- 使用`JUC`的`AtomicMarkableReference`，
- 使用`JUC`的`AtomicReferenceArray`，

#### 总结

总之，实现相对线程安全的单例模式方法有很多，但是，无论怎么实现，都必须保证只有一个实例，而且必须是线程安全的。

其实，为了保证线程安全，很多方法，都要写很多代码，代码极其臃肿。那么，有没有一种方法，可以保证线程安全，又可以保证代码精简呢？

其实，最简单的单例模式实现方式其实是枚举··· ···




       
