《面试题》一、Java基础

# 1 Java基础
## 1.1 JVM
### 1.1.1 JVM内存模型
1、JVM的内存模型是怎么样,那些是线程私有,那些是线程共有?
```
线程私有:
1、Java栈:和线程的生命周期相同,和线程的生命周期相同。
    栈桢的结构:
        本地变量表Local Variable;
        操作数栈Operand Stack;
        对运行时常量池的引用 Runtime Constant Reference;
    异常:
    线程请求的栈深度超过JVM允许深度StackOverflowError;
    若JVM允许动态扩展,若无法申请到足够内存 OutOfMemortError
2、本地方法栈:
    异常: 
    线程请求的栈深度超过JVM允许深度StackOverflowError;
    若JVM允许动态扩展,若无法申请到足够内存 OutOfMemortError;
3、程序计数器(PC寄存器):指向JVM字节码指令的地址、唯一一个无OOM的区域

线程共享:
1、方法区:运行时常量池(1.7之前放在方法区),1.8之后没有了----->元空间代替
2、Java堆:
    新生代(1/3):eden(8/10)、from survivor(1/10)、to survivor(1/10)
    老年代(2/3)
    永久代(1.8之后没有)
    常量池:运行时常量池(1.7之后)、字符串常量池
    异常(OOM)
    
Java堆和栈有什么区别?
- 每个线程都有自己的栈内存,所有的线程共享堆内存;
- 栈存储 本地变量、方法参数、栈调用,堆存储对象数据;

对象定位访问有哪两种方式?
- 句柄:如果使⽤句柄的话,那么Java堆中将会划分出⼀块内存来作为句柄池,reference 中
存储的就是对象的句柄地址,⽽句柄中包含了对象实例数据与类型数据各⾃的具体地址信
息;
- 直接指针:如果使⽤直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类
型数据的相关信息,⽽reference 中存储的直接就是对象的地址;
```
2、Java的内存模型
```
- Java的内存模型规定所以变量都是存在主存(类似物理内存)里面,每个线程都有自己的工作内存(类似于高速缓存)。线程对变量的操作必须在自己的工作内存中进行,而不能直接对主存进行操作,每个线程的工作内存的是隔离的。
- Java内存模型的volatile关键字和 原子性、可见性、有序性和happens-before关系等。
```
### 1.1.2 JVM的垃圾回收
1、JVM怎么判断对象已经死亡?
```
1、引用计数法(会有循环引用问题,导致内存泄漏,不推荐)
2、可达性分析算法(根搜索算法):以GC Roots作为起点向下搜索,当一个对象和任何GC Roots没有引用链相连(两次才回收);
    - GC Roots对象:VM栈中的引用、方法区中的静态引用、JNI(Java本地方法引用)
    - Java中的四种引用:强引用(可达状态,不可回收)、软引用(内存不足才会回收)、弱引用(无论内存是否充足都回收)、虚引用(不能单独使用,必须和引用队列联合使用,跟踪对象被垃圾回收的状态,“单弱多强”)
    - 三色标记:
        - 白色:尚未被GC访问过的对象,如果全部标记已完成依旧为白色的,称为不可达对象,既垃圾对象。
        - 灰色:本对象已访问过,但是本对象的子引用对象还没有被访问过,全部访问完会变成黑色,属于中间态。
        - 黑色:本对象已经被GC访问过,且本对象的子引用对象也已经被访问过了。
```
2、JVM的垃圾回收算法
```
1、复制算法:复制算法是将内存分为大小相同的两块,当这一块使用完了,就把当前存活的对象复制到另一块,然后一次性清空当前区块。此算法的缺点是只能利用一半的内存空间。
2、标记-清除算法:此算法执行分两阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。(CMS垃圾回收器)
3、标记-整理:此算法结合了“标记-清除”和“复制”两个算法的优点。分为两个阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。(G1垃圾回收器)
```
3、JVM的垃圾回收器
```
- 新生代
Serial :单线程,标记复制
ParNew :多线程,标记复制(Serial多线程版)
Parallel Scavenge :多线程,标记复制,可控制吞吐量(ParNew升级版)
- 老年代
Serial Old :单线程,标记整理
Parallel Old :多线程,标记整理

1、CMS
    过程:初始标记->并发标记->重新标记->并发清除;(标记-清除)
    优点:使用多线程,标记清除垃圾;
    缺点:对 CPU 资源要求敏感、CMS 无法清除浮动垃圾、CMS 垃圾回收会产生大量空间碎片;
2、G1 (JDK9以后的默认GC选项)
    优点:- 一种兼顾吞吐量和停顿时间的GC实现,低停顿时间,多线程,G1 可以直观的设定停顿时间的目标,相比于 CMS CG,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多;
          - 区域划分和优先级区域回收机制,类似棋盘的一个个 Region。Region 之间是复制算法,但整体上实际可看作是标记-整理(Mark-Compact)算法,不产生内存碎片,当 Java 堆非常大时,G1 的优势更加明显;
```
4、JVM的参数配置
```
-Xms:初始堆内存
-Xmx:最大堆内存
-Xmn:年轻代内存,Sun官方推荐配置为整个堆的3/8
-XX:+PrintGCDetails
-XX:SurvivorRatio=8 设置年轻代中Eden区与Survivor区的大小比值  1:1:8
-XX:PretenureSizeThreshold=xxx
-XX:MaxTenuringThreshold
-XX:-HandlePromotionFailure
-XX: +HeapDumpOnOutOfMemoryError
-XX: HeapDumpPath=/tmp/dump 配置dump文件输出路径
-XX: MetaspaceSize=512m
-XX: MaxMetaspaceSize=512m 设置元空间大小
```
5、MinorGC、MajorGC和FullGC
```
1.概念:
- MinorGC:当新对象生成,但是在Eden区申请空间失败时就会触发,存活的对象就会进入Survivor区,非常频繁。
- MajorGC: 是清理老年代,许多 Major GC 是由 Minor GC 触发的。
- FullGC: 对所有区域包括年轻代、老年代、永久代进行回收,所以比MinorGC慢很多,尽可能减少FullGC次数。
2.MinorGC和FullGC分别在什么时候发生?
MinorGC: Eden区空间占满时,MinorGC会将仍然存活的对象复制S0中去,这样Eden去就会被清空。又触发一次MinoGC,将S0和Eden中存活的复制到S1,S0和Eden被清空。同个时刻只有Eden和Survivor被操作。每次MinorGC时,有一个计数器会自动增加值,默认达到15次时(因为Object Header采用4个bit位来保存年龄,4个bit位能表示的最大数就是15)(默认晋升年龄并不都是 15,CMS垃圾收集器就是 6),JVM会停止复制,并把对象转移到老年代。
FullGC:待补充
注: ⼤对象就是需要⼤量连续内存空间的对象(⽐如:字符串、数组)。为什么要这样呢?为了避免为⼤对象分配内存时由于分配担保机制带来的复制⽽降低效率。

```
6、JVM内存状况分析工具
```
- jps:虚拟机进程状况工具
- jstat:虚拟机统计信息监视工具
- jinfo:Java配置信息工具
- jmap:Java内存映射工具(导出Dump文件,在MAT分析)
- jstack:Java堆栈信息跟踪工具
- VisualVM:多合一故障处理工具
- MAT:dump文件分析
    1.配置JVM参数,OOM时指定路径生成DUMP文件
    2.配置MAT参数,内存要大于DUMP文件
    3.分析DUMP饼图占用内存最大的的堆栈信息找到导致
- Arthas:阿里的jvm分析工具(https://arthas.aliyun.com/doc/)
- Prometheus:监控工具
```
### 1.1.3 Java的类加载机制
1、Java代码的执行
```
Java 源文件(.java)—->编译器(javac)—->字节码文件(.class)—->JVM—->机器码
```
2、双亲委派模型
```
1.类加载器
    - Application ClassLoader
    - Extension ClassLoader
    - Bootstrap ClassLoader

2.执行流程
a.调用自身的findLoadedClass()去内存中寻找这个类,然后通过AppClassLoader寻找它的父类;
b.如果找不到则会调用其父类加载器,即ExtClassLoader同样去内存中寻找;
c.如果找不到则会再通过父类加载器即BootStrap来寻找;
d.如果都没有找到,则会一层一层返回,即BootStrap通过它自身定义的加载路径加载;
e.如果加载不到则会通过ExtClassLoader根据自身定义的加载路径加载,如果加载不到则会通过AppClassLoader加载。如果都加载不到,会报出ClassNotFund异常。

3.为什么要这样做(双亲委派的作用)
- 保证了使用不同的类加载器终得到的都是同样一个Object对象;
- 避免了多份同样的字节码的加载,节省内存空间;
```
3、逃逸分析
```
什么是对象逃逸?
    - 对象和数组并不是都在堆上分配内存
    
JVM配置:
    -XX:+DoEscapeAnalysis:表示开启逃逸分析
    -XX:-DoEscapeAnalysis : 表示关闭逃逸分析 从jdk1.7开始已经默认开始逃逸分析,如需关闭,需要指定-XX:-DoEscapeAnalysis

三种优化:
1.同步省略(锁消除):如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
2.标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中
3.栈上分配:如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配
```
## 1.2 Java集合类
1、ConcurrentHashMap 1.7和1.8的区别
```
1、Segment分段锁->Node+syncronized同步代码块+CAS
    链表->红黑树:如果大于8且数组长度大于64->红黑树,如果大于8且数组长度小于64->数组扩容,小于6->链表
2、为什么要做改变
    - 取消Segment分段锁,尽量和HashMap保持一致
    - syncronized的优化使效率大大提高
3、为什么一定要用红黑树,不用二叉树
    - 二叉树在极端情况下会退化成链表,而红黑树是有balance不会退化成链表
    - 时间复杂度:链表O(n/2),红黑树O(log(n))
4、CAS加锁,比较轻量级,原子性操作
    - 问题:
    1.compareAndSet会存在ABA问题(通过版本号version标记对象,1.5之后提AtomicStampedReference<E>解决问题,它通过包装[E,Integer]的元组队对象标记版本戳stamp)
    2.自旋CAS消耗CPU性能;
    3.只能保证一个共享变量的原子操作;
5、JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock
    - 因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
    - JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
    - 在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据
```
2、HashMap
```
1、为什么加载因子是0.75? - 泊松分布得出的
2、1.7和1.8的区别?
    - 1.7链表采用头插法:在多线程操作会导致死循环
    - 1.8链表采用尾插法:避免死循环问题
3、快速失败和失败安全的区别
1.原理
- 快速失败:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
- 失败安全:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
```


## 1.3 Java并发
1、多线程、异步线程
```
1、实现多线程的三种方式
    - 继承Thread类
    - 实现Runable接口
    - 实现Callable接口(结合线程池使用)
    - 实现 Runnable 接⼝和 Callable 接⼝的区别:
        - Runnable 接⼝不会返回结果或抛出检查异常, run()
        - Callable 接⼝可以返回结果或抛出检查异常, call()

2、执⾏ execute()⽅法和 submit()⽅法区别
    - execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否
    - submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个Future类型的对象来判断任务是否执⾏成功,并通过 Future 的 get() ⽅法来获取返回值, get()⽅法会阻塞当前线程直到任务完成
3、Future
    - Future(1.5)
    - CompareableFuture(1.8)
```

2、线程池Executor分别有哪几种?核心参数?有什么好处?
```
- 四种线程池:
1.newSingleThreadExecutor单个线程
2.newFixedThreadPool固定线程数(适合执行单位时间内固定的任务数)
3.newScheduledThreadPool定时(适合以执行周期性的任务)
4.newCachedThreadPool 可缓存(适合执行短时间内大量任务)
注意:不允许使⽤ Executors 去创建?
    - FixedThreadPool 和 SingleThreadPool 允许LinkedBlockingQueue作为任务队列长度为 Integer.MAX_VALUE,可能会堆积大量请求,可能会导致内存溢出;
    - CachedThreadPool 和 ScheduledThreadPool 允许创建线程数量为 Integer.MAX_VALUE,创建大量线程,可能会导致内存溢出;


- 核心参数:
1.corePoolSize(核心线程数)
2.maximumPoolSize(最大线程数)
3.keepLiveTime(线程空闲时间)
4.unit(时间单位)
5.workQueue(缓存队列)
6.threadFactory(线程工厂)
7.handler(拒绝策略对象)
    - AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
    - DiscardPolicy:丢弃任务,但是不抛出异常
    - DiscardOldestPolicy:丢弃队列最前面的任务然后重新提交被拒绝的任务
    - CallerRunsPolicy:由调用主线程(提交任务的线程)处理该任务
    
- 好处:
1.通过重复利用已创建的线程,减少在创建销毁线程的时间;
2.提高响应速度;
3.提供线程的可管理性;
4.如果不使用线程池,有可能导致系统内存耗尽;

- 典型线程池组成:
1.线程池管理器
2.工作线程
3.请求接口
4.请求队列
5.结果队列

附:
- ThreadLocal的使用场景:Spring 的事务管理器、 Hibernate 的 Session 管理等。
- ThreadLocal线程安全的原因:ThreadLocal 为每一个线程维护变量的副本,把共享数据的可见范围限制在同一个线程之内,因此ThreadLocal是线程安全的,每个线程都有属于自己的变量
- ThreadLocal的内存泄漏问题
    - 原因: 由于 ThreadLocalMap 中的 key 是弱引用,所以一定就会被回收,而value是强引用,不会被回收,这样ThreadLocalMap 中就会出现 key 为 null 的 Entry,并且没有办法访问这些数据,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 并且永远无法回收,从而造成内存泄漏。
    - 解决方法: 在使用完 ThreadLocal 之后,调用remove() 方法,清除掉 ThreadLocalMap 中的无用数据就可以解决了内存溢出的问题。
```
3、乐观锁和悲观锁
```
- 乐观锁:
1、乐观锁CAS
    - 缺点:自旋循环时间太长、ABA问题
    - 解决办法:JDK1.5之后提供了一个类AtomicStampedReference来解决ABA问题
2、LongAdder
    - 实现原理:
    - 和CAS的区别:
3、JVM涉及的原子类
    - AtomicLong
    - AtomicInteger
    - Random
    - LockSupport.park()
    - ConcurrentHashMap

- 悲观锁:
1、概念
2、应用场景:MySQL的当前读、行锁、表锁、读锁、写锁等等;

如果并发不大,可以使用悲观锁解决并发问题;但是如果系统并发很大,悲观锁会有性能问题,因此应该采用乐观锁。
```
4、死锁是什么?怎么解决死锁问题?
```
1、产生条件
    - 互斥条件
    - 请求与保持条件 
    - 不可剥夺条件
    - 循环等待条件
2、如何避免死锁
    - 银行家算法(待补充)
    - 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、 ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。 尽量使用 Java.util.concurrent 并发类代替自己手写锁。 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。 尽量减少同步的代码块。
3、死锁和活锁的区别?
- 死锁会阻塞,活锁不会阻塞,但是会导致耗尽CPU资源;
- 死锁无法解开,活锁有一定几率解开;
- 死锁的表现为等待,而活锁是不断改变状态的;
```
5、AQS(AbstractQueuedSynchronizer)
```
1、概念: AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配,通过AQS完成对同步状态int类型的state变量值的修改,
2、核心部分:
    - private volatile int state(代表共享资源)
    - FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)
    - 自定义同步器
        - isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它;
        - tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false;
        - tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false; - tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源;
        - tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false;
3、设计模式:模板模式
    - 概念:AQS本身是抽象的,实现都是通过子类实现的,使用者继承AQS并重写指定的方法。(其主要工作基于CHL队列,voliate关键字修饰的状态符state,线程去修改状态符成功了就是获取成功,失败了就进队列等待,等待唤醒)
    - 涉及子类
        - 自旋锁、互斥锁、读者写锁、条件产量、信号量(Semaphore)、栅栏(CyclicBarrier)、倒计时计数器(CountDownLatch)
4、尾分叉
    - 概念:不断从链表尾到链表头的遍历,都设置为尾部,为了防止漏掉尾部节点,把自己设为尾部,从尾到头就能整个链表
```
6、syncronized和ReentrantLock 
```
1、syncronized锁升级过程:无锁->偏向锁->轻量级锁->重量级锁(不可降级,syncronized慎用)
    - 对象头里面的markword存放lock状态(2bit):
        - 01是无锁或者偏向锁;
        - 00是轻量级锁;
        - 10是重量级锁;
        - 11是GC标记,表示可以被GC了,被GC打了标记了;
        
    a.无锁->偏向锁
        - synchronized在第一个线程(线程1)进入的情况下,默认修改为偏向锁,将当前线程的ID更新到对象头的markword的线程ID中,增加偏向锁的时间戳以及偏向锁的标志修改为1,其他线程是在线程1未退出同步代码块前是没有办法进入同步代码块。
    b.偏向锁->轻量级锁
        - 线程1一直没有退出同步代码块,而线程2又要竞争这把锁,而线程2通过CAS自适应自旋一直没有成功(线程1退出了同步代码块,CAS成功是不会升级),这个时候它就升级为轻量级锁,升级过程很耗性能。
        - 偏向锁升级为轻量级锁时需要撤销偏向锁,过程:1、在一个安全点停止所有拥有锁的线程;2、遍历线程栈,如果存在锁记录,需要修复锁记录和markword,使其变成无锁的状态;3、唤醒当前线程,将当前锁升级为轻量级锁;
    c.轻量级锁->重量级锁
        - 在锁的竞争非常激励的情况下,比如线程1首先将在线程栈上开辟一定的空间来存储markword,并且相互指向,然后开始执行同步代码,而这个时候很多线程都过来了,那么这些线程也要拷贝markword到线程栈中,然后cas修改lockrecord与markword的相互指向,这时只有一个线程能够成功,其他线程都需要cas,如果线程1没有同步代码块没有指向完成,其他线程是没有办法自旋成功,那么就就那些锁膨胀,升级为重量级锁,重量级锁升级过后线程的阻塞是由内核进行处理的,所以性能较低。
注:竞争激烈可能从偏向锁直接升级到重量级锁

2、syncronized各个锁的优缺点
    偏向锁
        优点:加锁解锁不需要额外开销,性能高
        缺点:产生锁竞争时,撤销锁需要额外损耗
    轻量级锁
        优点:竞争锁不会阻塞
        缺点:如果得不到锁不断自旋会消耗性能
    重量级锁
        优点:竞争线程不用自旋,不会消耗CPU
        缺点:线程阻塞,响应慢
    适应性自旋锁
        自旋的次数会变,即上次自旋成功了,虚拟机下次就会分配更多次自旋机会,如果上次失败,下次分配自旋机会就会更少。目的是为了节约处理器的资源。
        ConcurrentHashMap从1.7(分段锁)->1.8(syncronized),去除了分段锁,采用syncronized,说明还是优化的不错的
        
3、ReentrantLock 常见方法如下:
- lock():用于获取锁
- unlock():用于释放锁
- tryLock():尝试获取锁
- getHoldCount():查询当前线程执行 lock() 方法的次数
- getQueueLength():返回正在排队等待获取此锁的线程数
- isFair():该锁是否为公平锁(默认非公平锁)

4、syncronized和ReentrantLock的区别:
- synchronized是Java内置关键字,在JVM层面,ReentrantLock是个Java类,JRE层面;
- ReentrantLock 使用起来比较灵活,但必须手动获取与释放锁,否则会造成死锁,而 synchronized 不需要手动释放和开启锁;
- ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等;
- ReentrantLock 可以通过tryLock()知道有没有成功获得锁,而synchronized无法办到;
- ReentrantLock 在竞争激烈的情况下性能略高于 synchronized;
- ReentrantLock(可中断锁,lockInterruptibly())syncronized(不可中断锁)
- ReentrantLock可以实现公平锁,syncronized是非公平锁
```
7、volatile原理
```
1、volatile 的作用是什么?
- volatile 是 Java 虚拟机提供的最轻量级的同步机制。
- 当变量被定义成 volatile 之后,具备两种特性:
    - (可见性)保证此变量对所有线程的可见性,当一条线程修改了这个变量的值,修改的新值对于其他线程是可见的(可以立即得知的);
        1.使用volatile关键字会强制将修改的值立刻写入主存;
        2.当线程2进行修改时,会导致线程1的工作内存的缓存变量stop的缓存行失效;
        3.由于线程1的工作内存中缓存变量stop缓存行无效,所以线程1再次读取变量stop时会去主存读
    - (有序性)禁止指令重排序优化,普通变量仅仅能保证在该方法执行过程中,得到正确结果,但是不保证程序代码的执行顺序。
        内存屏障:
            - 先于这个内存屏障的指令必须先执行,后与这个内存屏障的指令必须后执行
            - 使得内存可见性,读指令前插入读屏障,可以使高速缓存的数据失效,重新从主存加载,写指令之后插入写屏障,能让写入缓存的最新数据写回主存。
            - 四种类型的屏障:
                1.LoadLoad屏障:读|读
                2.StoreStore屏障:写|写
                3.LoadStore屏障:读|写
                4.StoreLoad屏障:写|读
- 注:双重检验锁DCL——使用volatile的场景之一
(待补充)
```

## 1.4 设计模式
### 1.4.1 创建型
1. 简单工厂模式:
2. 抽象工厂模式:由子类决定要创建的具体类是哪一个
```
public class AbstractFactoryTest {
   public static void main(String[] args) {
       // 抽象工厂
       String result = (new CoffeeFactory()).createProduct("Latte");
       System.out.println(result); // output:拿铁
   }
}
// 抽象工厂
abstract class AbstractFactory{
   public abstract String createProduct(String product);
}
// 啤酒工厂
class BeerFactory extends AbstractFactory{
   @Override
   public String createProduct(String product) {
       String result = null;
       switch (product) {
           case "Hans":
               result = "汉斯";
               break;
           case "Yanjing":
               result = "燕京";
               break;
           default:
               result = "其他啤酒";
               break;
       }
       return result;
   }
}
/*
 * 咖啡工厂
 */
class CoffeeFactory extends AbstractFactory{
   @Override
   public String createProduct(String product) {
       String result = null;
       switch (product) {
           case "Mocca":
               result = "摩卡";
               break;
           case "Latte":
               result = "拿铁";
               break;
           default:
               result = "其他咖啡";
               break;
       }
       return result;
   }
}
```
3. 单例模式:确保有且只有一个对象被创建,单例模式解决高并发的问题:采用双检锁 (读写锁)
4. 原型模式:clone, 深拷贝,浅拷贝
5. 建造者模式:隐藏具体的建造过程及细节
### 1.4.2 结构型
1. 适配器模式:将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。
```
/*
 * 传统的充电线 MicroUSB
 */
interface MicroUSB {
    void charger();
}
/*
 * TypeC 充电口
 */
interface ITypeC {
    void charger();
}
class TypeC implements ITypeC {
    @Override
    public void charger() {
        System.out.println("TypeC 充电");
    }
}
/*
 * 适配器
 */
class AdapterMicroUSB implements MicroUSB {
    private TypeC typeC;

    public AdapterMicroUSB(TypeC typeC) {
        this.typeC = typeC;
    }

    @Override
    public void charger() {
        typeC.charger();
    }
}
/*
 * 测试调用
 */
public class AdapterTest {
    public static void main(String[] args) {
        TypeC typeC = new TypeC();
        MicroUSB microUSB = new AdapterMicroUSB(typeC);
        microUSB.charger();

    }
}
```
2. 装饰者模式:给对象附加不同的责任或功能
1)定义顶层对象,定义行为
```
    interface IPerson {
        void show();
    }
```
2)定义装饰器超类
```
    class DecoratorBase implements IPerson{
        IPerson iPerson;
        public DecoratorBase(IPerson iPerson){
            this.iPerson = iPerson;
        }
        @Override
        public void show() {
            iPerson.show();
        }
    }
```
3)定义具体装饰器
```
    class Jacket extends DecoratorBase {
        public Jacket(IPerson iPerson) {
            super(iPerson);
        }
        @Override
        public void show() {
            // 执行已有功能
            iPerson.show();
            // 定义新行为
            System.out.println("穿上夹克");
        }
    }
    class Hat extends DecoratorBase {
        public Hat(IPerson iPerson) {
            super(iPerson);
        }
        @Override
        public void show() {
            // 执行已有功能
            iPerson.show();
            // 定义新行为
            System.out.println("戴上帽子");
        }
    }
```
4)定义具体对象
```
    class LaoWang implements IPerson{
        @Override
        public void show() {
            System.out.println("什么都没穿");
        }
    }
```
5)装饰器模式调用
```
    public class DecoratorTest {
        public static void main(String[] args) {
            LaoWang laoWang = new LaoWang();
            Jacket jacket = new Jacket(laoWang);
            Hat hat = new Hat(jacket);
            hat.show();
        }
    }
```
3. 外观模式:简化一群类的接口
4. 组合模式:客户用一致的方式处理对象集合和单个对象
5. 桥接模式:抽象与实现分离以实现不同的
6. 代理模式:你的经纪人
```
/*
 * 定义售票接口
 */
interface IAirTicket {
    void buy();
}
/*
 * 定义飞机场售票
 */
class AirTicket implements IAirTicket {
    @Override
    public void buy() {
        System.out.println("买票");
    }
}
/*
 * 代理售票平台
 */
class ProxyAirTicket implements IAirTicket {
    private AirTicket airTicket;
    public ProxyAirTicket() {
        airTicket = new AirTicket();
    }
    @Override
    public void buy() {
        airTicket.buy();
    }
}
/*
 * 代理模式调用
 */
public class ProxyTest {
    public static void main(String[] args) {
        IAirTicket airTicket = new ProxyAirTicket();
        airTicket.buy();
    }
}
```
7. 享元模式:共享技术,支持大量细粒度的对象
### 1.4.3行为型
1. 策略模式:封装可以互换的行为,并使用委托来决定要使用哪一个。(当if else太多时可以采用策略模式)
```
/*
 * 声明旅行
 */
interface ITrip {
    void going();
}
class Bike implements ITrip {
    @Override
    public void going() {
        System.out.println("骑自行车");
    }
}
class Drive implements ITrip {
    @Override
    public void going() {
        System.out.println("开车");
    }
}
/*
 * 定义出行类
 */
class Trip {
    private ITrip trip;

    public Trip(ITrip trip) {
        this.trip = trip;
    }

    public void doTrip() {
        this.trip.going();
    }
}
/*
 * 执行方法
 */
public class StrategyTest {
    public static void main(String[] args) {
        Trip trip = new Trip(new Bike());
        trip.doTrip();
    }
}
```
2. 观察者模式:让对象能够在状态改变时被通知
1)定义观察者(消息接收方)
```

    /*
     * 观察者(消息接收方)
     */
    interface Observer {
        public void update(String message);
    }
    /*
     * 具体的观察者(消息接收方)
     */
    class ConcrereObserver implements Observer {
        private String name;
    
        public ConcrereObserver(String name) {
            this.name = name;
        }
    
        @Override
        public void update(String message) {
            System.out.println(name + ":" + message);
        }
    }
```
2)定义被观察者(消息发送方)
```
    /*
     * 被观察者(消息发布方)
     */
    interface Subject {
        // 增加订阅者
        public void attach(Observer observer);
        // 删除订阅者
        public void detach(Observer observer);
        // 通知订阅者更新消息
        public void notify(String message);
    }
    /*
     * 具体被观察者(消息发布方)
     */
    class ConcreteSubject implements Subject {
        // 订阅者列表(存储信息)
        private List<Observer> list = new ArrayList<Observer>();
        @Override
        public void attach(Observer observer) {
            list.add(observer);
        }
        @Override
        public void detach(Observer observer) {
            list.remove(observer);
        }
        @Override
        public void notify(String message) {
            for (Observer observer : list) {
                observer.update(message);
            }
        }
    }
```
3)代码调用
```
    public class ObserverTest {
        public static void main(String[] args) {
            // 定义发布者
            ConcreteSubject concreteSubject = new ConcreteSubject();
            // 定义订阅者
            ConcrereObserver concrereObserver = new ConcrereObserver("老王");
            ConcrereObserver concrereObserver2 = new ConcrereObserver("Java");
            // 添加订阅
            concreteSubject.attach(concrereObserver);
            concreteSubject.attach(concrereObserver2);
            // 发布信息
            concreteSubject.notify("更新了");
        }
    }
```
程序执行结果如下:
```
老王:更新了
Java:更新了
```
3. 模板方法模式:只定义方法,由子类决定如何实现一个算法中的步骤(AQS)
4. 命令模式:封装请求成为对象
5. 状态模式:封装了基于状态的行为,并使用委托在行为之间切换
6. 迭代器模式:在对象的集合之间游走,而不暴露集合的实现
7. 责任链模式:和链表有点像,每个责任者都保存了要设置的下一个处理对象的指针或引用
8. 中介者模式:联合国,处理各国纠纷
9. 解释器模式:编译器
10. 备忘录模式: 游戏存档
11. 访问者模式:最复杂的模式,适用于数据结构相对稳定的系统

# 2 基础框架
## 2.1 Spring/Springboot/SpringMVC
1、@Autowired和@Resouce区别
```
- @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用;
- @Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配;

例如:RedisTemplate双亲委派出现的问题
```
2、Springboot的starter原理
```
原理:利用starter实现自动化配置只需要两个条件——maven依赖、配置文件,这里简单介绍下starter实现自动化配置的流程。
引入maven实质上就是导入jar包,spring-boot启动的时候会找到starter jar包中的resources/META-INF/spring.factories文件,根据spring.factories文件中的配置,找到需要自动配置的类。
    - 配置如下:
        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.ouyanglol.starterdemo.config.DemoAutoConfiguration
    - 用到的注解:
        @Configuration    表明是一个配置文件,被注解的类将成为一个bean配置类
        @ConditionalOnClass    当classpath下发现该类的情况下进行自动配置
        @ConditionalOnBean    当classpath下发现该类的情况下进行自动配置
        @EnableConfigurationProperties    使@ConfigurationProperties注解生效
        @AutoConfigureAfter    完成自动配置后实例化这个bean
参考博客:https://blog.csdn.net/Mr_OOO/article/details/89477948
```

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值