diff --git "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" index 79837e34553..d763cb83492 100644 --- "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" +++ "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" @@ -54,7 +54,7 @@ ``` -细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会降到这一点内容! +细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容! ## 二 一步一步分析 ArrayList 扩容机制 @@ -308,7 +308,7 @@ ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没 ``` -**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量从新分配的次数** +**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量重新分配的次数** 我们通过下面的代码实际测试以下这个方法的效果: @@ -344,4 +344,4 @@ public class EnsureCapacityTest { ``` -通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量从新分配的次数 +通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数 diff --git "a/Java\347\233\270\345\205\263/ArrayList.md" "b/Java\347\233\270\345\205\263/ArrayList.md" index d6792edb241..c3e8dd47896 100644 --- "a/Java\347\233\270\345\205\263/ArrayList.md" +++ "b/Java\347\233\270\345\205\263/ArrayList.md" @@ -1,4 +1,3 @@ - - [ArrayList简介](#arraylist简介) @@ -19,15 +18,15 @@ 它继承于 **AbstractList**,实现了 **List**, **RandomAccess**, **Cloneable**, **java.io.Serializable** 这些接口。 在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为**O(n)**,求表长以及增加元素,取第 i 元素的时间复杂度为**O(1)** - +   ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。 - -  ArrayList 实现了**RandomAccess 接口**,即提供了随机访问功能。RandomAccess 是 Java 中用来被 List 实现,为 List 提供**快速访问功能**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 - + +  ArrayList 实现了**RandomAccess 接口**, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 +   ArrayList 实现了**Cloneable 接口**,即覆盖了函数 clone(),**能被克隆**。 - +   ArrayList 实现**java.io.Serializable 接口**,这意味着ArrayList**支持序列化**,**能通过序列化去传输**。 - +   和 Vector 不同,**ArrayList 中的操作不是线程安全的**!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。 ### ArrayList核心源码 @@ -85,7 +84,7 @@ public class ArrayList extends AbstractList } /** - *默认构造函数,其默认初始容量为10 + *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; @@ -177,7 +176,7 @@ public class ArrayList extends AbstractList newCapacity = minCapacity; //再检查新容量是否超出了ArrayList所定义的最大容量, //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, - //如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为 minCapacity。 + //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: @@ -631,7 +630,7 @@ public class ArrayList extends AbstractList newCapacity = minCapacity; //再检查新容量是否超出了ArrayList所定义的最大容量, //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, - //如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为 minCapacity。 + //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: @@ -653,15 +652,15 @@ public class ArrayList extends AbstractList 3. .java 中的**size()方法**是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! - + #### 内部类 ```java (1)private class Itr implements Iterator (2)private class ListItr extends Itr implements ListIterator (3)private class SubList extends AbstractList implements RandomAccess (4)static final class ArrayListSpliterator implements Spliterator -``` -  ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**,**next()**,**remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**,**nextIndex()**,**previousIndex()**,**previous()**,**set(E e)**,**add(E e)**等方法,所以这也可以看出了**Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。 +``` +  ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**,**next()**,**remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**,**nextIndex()**,**previousIndex()**,**previous()**,**set(E e)**,**add(E e)**等方法,所以这也可以看出了 **Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。 ### ArrayList经典Demo ```java diff --git "a/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" "b/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" new file mode 100644 index 00000000000..c5ec6dddd04 --- /dev/null +++ "b/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" @@ -0,0 +1,347 @@ +熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础。 + + + +- [BIO,NIO,AIO 总结](#bionioaio-总结) + - [1. BIO \(Blocking I/O\)](#1-bio-blocking-io) + - [1.1 传统 BIO](#11-传统-bio) + - [1.2 伪异步 IO](#12-伪异步-io) + - [1.3 代码示例](#13-代码示例) + - [1.4 总结](#14-总结) + - [2. NIO \(New I/O\)](#2-nio-new-io) + - [2.1 NIO 简介](#21-nio-简介) + - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别) + - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io(非阻塞io)) + - [2)Buffer\(缓冲区\)](#2buffer缓冲区) + - [3)Channel \(通道\)](#3channel-通道) + - [4)Selectors\(选择器\)](#4selectors选择器) + - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式) + - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍) + - [2.5 代码示例](#25-代码示例) + - [3. AIO \(Asynchronous I/O\)](#3-aio-asynchronous-io) + - [参考](#参考) + + + + +# BIO,NIO,AIO 总结 + + Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。 + +在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。 + +**同步与异步** + +- **同步:** 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。 +- **异步:** 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 + +同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。 + +**阻塞和非阻塞** + +- **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 +- **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。 + +举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。 + + +## 1. BIO (Blocking I/O) + +同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。 + +### 1.1 传统 BIO + +BIO通信(一请求一应答)模型图如下(图源网络,原出处不明): + +![传统BIO通信模型图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2.png) + +采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。 + +如果要让 **BIO 通信模型** 能够同时处理多个客户端请求,就必须使用多线程(主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 **一请求一应答通信模型** 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 **线程池机制** 改善,线程池还可以让线程的创建和回收成本相对较低。使用`FixedThreadPool` 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节"伪异步 BIO"中会详细介绍到。 + +**我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?** + +在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。 + +### 1.2 伪异步 IO + +为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。 + +伪异步IO模型图(图源网络,原出处不明): + +![伪异步IO模型图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3.png) + +采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 + +伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。 + +### 1.3 代码示例 + +下面代码中演示了BIO通信(一请求一应答)模型。我们会在客户端创建多个线程依次连接服务端并向其发送"当前时间+:hello world",服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客,原地址如下: + +[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a) + +**客户端** + +```java +/** + * + * @author 闪电侠 + * @date 2018年10月14日 + * @Description:客户端 + */ +public class IOClient { + + public static void main(String[] args) { + // TODO 创建多个线程,模拟多个客户端连接服务端 + new Thread(() -> { + try { + Socket socket = new Socket("127.0.0.1", 3333); + while (true) { + try { + socket.getOutputStream().write((new Date() + ": hello world").getBytes()); + Thread.sleep(2000); + } catch (Exception e) { + } + } + } catch (IOException e) { + } + }).start(); + + } + +} + +``` + +**服务端** + +```java +/** + * @author 闪电侠 + * @date 2018年10月14日 + * @Description: 服务端 + */ +public class IOServer { + + public static void main(String[] args) throws IOException { + // TODO 服务端处理客户端连接请求 + ServerSocket serverSocket = new ServerSocket(3333); + + // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理 + new Thread(() -> { + while (true) { + try { + // 阻塞方法获取新的连接 + Socket socket = serverSocket.accept(); + + // 每一个新的连接都创建一个线程,负责读取数据 + new Thread(() -> { + try { + int len; + byte[] data = new byte[1024]; + InputStream inputStream = socket.getInputStream(); + // 按字节流方式读取数据 + while ((len = inputStream.read(data)) != -1) { + System.out.println(new String(data, 0, len)); + } + } catch (IOException e) { + } + }).start(); + + } catch (IOException e) { + } + + } + }).start(); + + } + +} +``` + +### 1.4 总结 + +在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 + + + +## 2. NIO (New I/O) + +### 2.1 NIO 简介 + + NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。 + +NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。 + +### 2.2 NIO的特性/NIO与IO区别 + +如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。 + +#### 1)Non-blocking IO(非阻塞IO) + +**IO流是阻塞的,NIO流是不阻塞的。** + +Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 + +Java IO的各种流是阻塞的。这意味着,当一个线程调用 `read()` 或 `write()` 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了 + +#### 2)Buffer(缓冲区) + +**IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。** + +Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。 + +在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。 + +最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。 + +#### 3)Channel (通道) + +NIO 通过Channel(通道) 进行读写。 + +通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。 + +#### 4)Selectors(选择器) + +NIO有选择器,而IO没有。 + +选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。 + +![一个单线程中Slector维护3个Channel的示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Slector.png) + +### 2.3 NIO 读数据和写数据方式 +通常来说NIO中的所有IO都是从 Channel(通道) 开始的。 + +- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。 +- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。 + +数据读取和写入操作图示: + +![NIO读写数据的方式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/NIO读写数据的方式.png) + + +### 2.4 NIO核心组件简单介绍 + +NIO 包含下面几个核心的组件: + +- Channel(通道) +- Buffer(缓冲区) +- Selector(选择器) + +整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。 + +### 2.5 代码示例 + +代码示例出自闪电侠的博客,原地址如下: + +[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a) + +客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。 + +```java +/** + * + * @author 闪电侠 + * @date 2019年2月21日 + * @Description: NIO 改造后的服务端 + */ +public class NIOServer { + public static void main(String[] args) throws IOException { + // 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程, + // 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等 + Selector serverSelector = Selector.open(); + // 2. clientSelector负责轮询连接是否有数据可读 + Selector clientSelector = Selector.open(); + + new Thread(() -> { + try { + // 对应IO编程中服务端启动 + ServerSocketChannel listenerChannel = ServerSocketChannel.open(); + listenerChannel.socket().bind(new InetSocketAddress(3333)); + listenerChannel.configureBlocking(false); + listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT); + + while (true) { + // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms + if (serverSelector.select(1) > 0) { + Set set = serverSelector.selectedKeys(); + Iterator keyIterator = set.iterator(); + + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + + if (key.isAcceptable()) { + try { + // (1) + // 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector + SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); + clientChannel.configureBlocking(false); + clientChannel.register(clientSelector, SelectionKey.OP_READ); + } finally { + keyIterator.remove(); + } + } + + } + } + } + } catch (IOException ignored) { + } + }).start(); + new Thread(() -> { + try { + while (true) { + // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms + if (clientSelector.select(1) > 0) { + Set set = clientSelector.selectedKeys(); + Iterator keyIterator = set.iterator(); + + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + + if (key.isReadable()) { + try { + SocketChannel clientChannel = (SocketChannel) key.channel(); + ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + // (3) 面向 Buffer + clientChannel.read(byteBuffer); + byteBuffer.flip(); + System.out.println( + Charset.defaultCharset().newDecoder().decode(byteBuffer).toString()); + } finally { + keyIterator.remove(); + key.interestOps(SelectionKey.OP_READ); + } + } + + } + } + } + } catch (IOException ignored) { + } + }).start(); + + } +} +``` + +为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题: + +- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100% +- 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug + +Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。 + +### 3. AIO (Asynchronous I/O) + +AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。 + +AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:[《漫话:如何给女朋友解释什么是Linux的五种IO模型?》](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect) ) + +查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。 + +## 参考 + +- 《Netty 权威指南》第二版 +- https://zhuanlan.zhihu.com/p/23488863 (美团技术团队) diff --git "a/Java\347\233\270\345\205\263/HashMap.md" "b/Java\347\233\270\345\205\263/HashMap.md" index 19b77c50828..45fad50cdb3 100644 --- "a/Java\347\233\270\345\205\263/HashMap.md" +++ "b/Java\347\233\270\345\205\263/HashMap.md" @@ -22,7 +22,7 @@ JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体 ## 底层数据结构分析 ### JDK1.8之前 -JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** **所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** @@ -90,15 +90,17 @@ public class HashMap extends AbstractMap implements Map, Cloneabl transient int modCount; // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 int threshold; - // 填充因子 + // 加载因子 final float loadFactor; } ``` - **loadFactor加载因子** - loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,load Factor越小,也就是趋近于0, + loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,数组中存放的数据(entry)也就越少,也就越稀疏。 - **loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值**。   + **loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值**。 + + 给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 - **threshold** @@ -171,23 +173,23 @@ static final class TreeNode extends LinkedHashMap.Entry { ![四个构造方法](https://user-gold-cdn.xitu.io/2018/3/20/162410d912a2e0e1?w=336&h=90&f=jpeg&s=26744) ```java // 默认构造函数。 - public More ...HashMap() { + public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } // 包含另一个“Map”的构造函数 - public More ...HashMap(Map m) { + public HashMap(Map m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false);//下面会分析到这个方法 } // 指定“容量大小”的构造函数 - public More ...HashMap(int initialCapacity) { + public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 指定“容量大小”和“加载因子”的构造函数 - public More ...HashMap(int initialCapacity, float loadFactor) { + public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) @@ -399,7 +401,7 @@ final Node[] resize() { else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { - signifies using defaults + // signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } diff --git "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" index f673332a1ce..00e67f7d779 100644 --- "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -67,13 +67,13 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期 ## get和post请求的区别 -> 网上也有文章说:get和post请求实际上是没有区别,大家可以自行查询相关文章!我下面给出的只是一种常见的答案。 +> 网上也有文章说:get和post请求实际上是没有区别,大家可以自行查询相关文章(参考文章:[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html),知乎对应的问题链接:[get和post区别?](https://www.zhihu.com/question/28586791))!我下面给出的只是一种常见的答案。 ①get请求用来从服务器上获得资源,而post是用来向服务器提交数据; ②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL; -③get传输的数据要受到URL长度限制(1024字节即256个字符);而post可以传输大量的数据,上传文件通常要使用post方式; +③get传输的数据要受到URL长度限制(最大长度是 2048 个字符);而post可以传输大量的数据,上传文件通常要使用post方式; ④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post; @@ -98,7 +98,7 @@ Form标签里的method的属性为get时调用doGet(),为post时调用doPost() ```java request.getRequestDispatcher("login_success.jsp").forward(request, response); ``` -**重定向(Redirect)** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。 +**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 `HttpServletResponse` 的 `setStatus(int status)` 方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。 1. **从地址栏显示来说** @@ -123,9 +123,9 @@ redirect:低. ## 自动刷新(Refresh) 自动刷新不仅可以实现一段时间之后自动跳转到另一个页面,还可以实现一段时间之后自动刷新本页面。Servlet中通过HttpServletResponse对象设置Header属性实现自动刷新例如: ```java -Response.setHeader("Refresh","1000;URL=http://localhost:8080/servlet/example.htm"); +Response.setHeader("Refresh","5;URL=http://localhost:8080/servlet/example.htm"); ``` -其中1000为时间,单位为毫秒。URL指定就是要跳转的页面(如果设置自己的路径,就会实现没过一秒自动刷新本页面一次) +其中5为时间,单位为秒。URL指定就是要跳转的页面(如果设置自己的路径,就会实现每过5秒自动刷新本页面一次) ## Servlet与线程安全 diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 17335c950ef..57fa9906fad 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,55 +1,60 @@ + + - [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) - [面向过程](#面向过程) - [面向对象](#面向对象) - [2. Java 语言有哪些特点](#2-java-语言有哪些特点) -- [3. 什么是 JDK 什么是 JRE 什么是 JVM 三者之间的联系与区别](#3-什么是-jdk-什么是-jre-什么是-jvm-三者之间的联系与区别) -- [4. 什么是字节码 采用字节码的最大好处是什么](#4-什么是字节码-采用字节码的最大好处是什么) - - [先看下 java 中的编译器和解释器:](#先看下-java-中的编译器和解释器:) - - [采用字节码的好处:](#采用字节码的好处:) +- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答) + - [JVM](#jvm) + - [JDK 和 JRE](#jdk-和-jre) +- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比) - [5. Java和C++的区别](#5-java和c的区别) - [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) - [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别) - [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) - [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) - [10. 重载和重写的区别](#10-重载和重写的区别) -- [11. Java 面向对象编程三大特性:封装、继承、多态](#11-java-面向对象编程三大特性封装、继承、多态) +- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态) - [封装](#封装) - [继承](#继承) - [多态](#多态) -- [12. String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-和-stringbuffer、stringbuilder-的区别是什么-string-为什么是不可变的) -- [13. 自动装箱与拆箱](#13-自动装箱与拆箱) +- [12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的) +- [13. 自动装箱与拆箱](#13-自动装箱与拆箱) - [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的) - [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) - [16. import java和javax有什么区别](#16-import-java和javax有什么区别) - [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么) - [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些) -- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符?对象实体与对象引用有何不同?) -- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值?返回值在类的方法里的作用是什么?) -- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 为什么](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法,该程序能正确执行吗-为什么) +- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同) +- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么) +- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) - [22. 构造方法有哪些特性](#22-构造方法有哪些特性) - [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同) -- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等,两者有什么不同?) -- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?) +- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同) +- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是) - [26. == 与 equals\(重要\)](#26--与-equals重要) -- [27. hashCode 与 equals(重要)](#27-hashcode-与-equals(重要)) +- [27. hashCode 与 equals \(重要\)](#27-hashcode-与-equals-重要) - [hashCode()介绍](#hashcode()介绍) - [为什么要有 hashCode](#为什么要有-hashcode) - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) - [28. 为什么Java中只有值传递](#28-为什么java中只有值传递) -- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程,程序、进程的基本概念。以及他们之间关系是什么) -- [30. 线程有哪些基本状态?这些状态是如何定义的?](#30-线程有哪些基本状态?这些状态是如何定义的) +- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么) +- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态) - [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) - [32 Java 中的异常处理](#32-java-中的异常处理) - [Java异常类层次结构图](#java异常类层次结构图) - - [Trowable类常用方法](#trowable类常用方法) + - [Throwable类常用方法](#throwable类常用方法) - [异常处理总结](#异常处理总结) -- [33 Java序列话中如果有些字段不想进行序列化 怎么办](#33-java序列话中如果有些字段不想进行序列化-怎么办) -- [Java基础学习书籍推荐](#java基础学习书籍推荐) +- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办) +- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法) +- [参考](#参考) + + ## 1. 面向对象和面向过程的区别 ### 面向过程 @@ -75,39 +80,52 @@ 7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); 8. 编译与解释并存; -## 3. 什么是 JDK 什么是 JRE 什么是 JVM 三者之间的联系与区别 +## 3. 关于 JVM JDK 和 JRE 最详细通俗的解答 + +### JVM -这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。 +Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。 -**JDK:** 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。 +**什么是字节码?采用字节码的好处是什么?** -**JRE:** 普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装JDK来编译、调试程序。 +> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 -**JVM:** 当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。 +**Java 程序从源代码到运行一般有下面3步:** -**区别与联系:** +![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png) - 1. JDK 用于开发,JRE 用于运行java程序 ; - 2. JDK 和 JRE 中都包含 JVM ; - 3. JVM 是 java 编程语言的核心并且具有平台独立性。 +我们需要格外注意的是 .class->机器码 这一步。在这一步 jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,所以后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。 -## 4. 什么是字节码 采用字节码的最大好处是什么 +> HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。 -### 先看下 java 中的编译器和解释器:    +总结:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 -Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。 +### JDK 和 JRE -编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。 +JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。 -每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。 +JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。 - Java 源代码---->编译器---->jvm 可执行的 Java 字节码(即虚拟指令)---->jvm---->jvm 中解释器----->机器可执行的二进制机器码---->程序运行。 +如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。 -### 采用字节码的好处:  +## 4. Oracle JDK 和 OpenJDK 的对比 -**Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。** +可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面通过我通过我收集到一些资料对你解答这个被很多人忽视的问题。 -> 解释性语言:解释型语言,是在运行的时候将程序翻译成机器语言。解释型语言的程序不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低。——百度百科 +对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案: + +> 问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别? +> +> 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。 + +总结: + +1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次; +2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的; +3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题; +4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能; +5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本; +6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 ## 5. Java和C++的区别 @@ -146,7 +164,7 @@ Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了 **重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 -## 11. Java 面向对象编程三大特性:封装、继承、多态 +## 11. Java 面向对象编程三大特性: 封装 继承 多态 ### 封装 @@ -164,11 +182,11 @@ Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了 ### 多态 -所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 +所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 -## 12. String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的 +## 12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的 **可变性**   @@ -198,14 +216,14 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 **性能** -每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 +每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 **对于三者使用的总结:** 1. 操作少量的数据 = String 2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder 3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer -## 13. 自动装箱与拆箱 +## 13. 自动装箱与拆箱 **装箱**:将基本类型用它们对应的引用类型包装起来; **拆箱**:将包装类型转换为基本数据类型; @@ -223,7 +241,7 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 所以,实际上java和javax没有区别。这都是一个名字。 -## 17. 接口和抽象类的区别是什么 +## 17. 接口和抽象类的区别是什么 1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法 2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 @@ -231,22 +249,24 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 -## 18. 成员变量与局部变量的区别有那些 +备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)) + +## 18. 成员变量与局部变量的区别有那些 1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰; -2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存 -3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 -4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰但没有被 static 修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。 +2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存 +3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 +4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。 -## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? +## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。 -## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么? +## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么? 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! -## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 为什么 +## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么? 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 @@ -262,15 +282,15 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象 2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制. -## 24. 对象的相等与指向他们的引用相等,两者有什么不同? +## 24. 对象的相等与指向他们的引用相等,两者有什么不同? 对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。 -## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? +## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? 帮助子类做初始化工作。 -## 26. == 与 equals(重要) +## 26. == 与 equals(重要) **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) @@ -307,7 +327,7 @@ public class test1 { -## 27. hashCode 与 equals(重要) +## 27. hashCode 与 equals (重要) 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” @@ -321,7 +341,7 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 **我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** -当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 +当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 @@ -339,7 +359,7 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 [为什么Java中只有值传递?](https://github.com/Snailclimb/Java-Guide/blob/master/%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87/%E6%9C%80%E6%9C%80%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84Java%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md) -## 29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么 +## 29. 简述线程,程序,进程的基本概念.以及他们之间关系是什么? **线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 @@ -348,34 +368,17 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 **进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。 -## 30. 线程有哪些基本状态?这些状态是如何定义的? - -1. **新建(new)**:新创建了一个线程对象。 -2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。 -3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。 -4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: -(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。 -(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 -(三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 -5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。 +## 30. 线程有哪些基本状态? -![](https://user-gold-cdn.xitu.io/2018/8/9/1651f19d7c4e93a3?w=876&h=492&f=png&s=128092) +参考《Java 并发编程艺术》4.1.4节。 -备注: 可以用早起坐地铁来比喻这个过程: +Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态。 -还没起床:sleeping +![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png) -起床收拾好了,随时可以坐地铁出发:Runnable +线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示: -等地铁来:Waiting - -地铁来了,但要排队上地铁:I/O阻塞 - -上了地铁,发现暂时没座位:synchronized阻塞 - -地铁上找到座位:Running - -到达目的地:Dead +![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png) ## 31 关于 final 关键字的一些总结 @@ -383,15 +386,15 @@ final关键字主要用在三个地方:变量、方法、类。 1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 -3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。 +3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 ## 32 Java 中的异常处理 ### Java异常类层次结构图 -![Java异常类层次结构图](http://images2015.cnblogs.com/blog/641003/201607/641003-20160706232044280-355354790.png) +![Java异常类层次结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png) 在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 - + **Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。 @@ -420,21 +423,31 @@ final关键字主要用在三个地方:变量、方法、类。 3. 程序所在的线程死亡。 4. 关闭CPU。 -## 33 Java序列话中如果有些字段不想进行序列化 怎么办 +## 33 Java序列化中如果有些字段不想进行序列化 怎么办 对于不想进行序列化的变量,使用transient关键字修饰。 transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。 -# Java基础学习书籍推荐 +## 34 获取用键盘输入常用的的两种方法 -**《Head First Java.第二版》:** -可以说是我的 Java 启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 +方法1:通过 Scanner -**《Java核心技术卷1+卷2》:** -很棒的两本书,建议有点 Java 基础之后再读,介绍的还是比较深入的,非常推荐。 +```java +Scanner input = new Scanner(System.in); +String s = input.nextLine(); +input.close(); +``` + +方法2:通过 BufferedReader -**《Java编程思想(第4版)》:** -这本书要常读,初学者可以快速概览,中等程序员可以深入看看 Java,老鸟还可以用之回顾 Java 的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 +```java +BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); +String s = input.readLine(); +``` +## 参考 +- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre +- https://www.educba.com/oracle-vs-openjdk/ +- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top diff --git "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" index d18c68e0020..cb0bd1fe0e3 100644 --- "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ "b/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" @@ -40,7 +40,7 @@ ## Arraylist 与 LinkedList 区别 -Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。 +Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList 底层使用的是双向链表数据结构(插入,删除效率特别高)(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。 ## ArrayList 与 Vector 区别 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector @@ -76,7 +76,8 @@ Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在 **==与equals的区别** 1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同 -2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同 +2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较 +3. ==指引用是否相同 equals()指的是值是否相同 ## comparable 和 comparator的区别 - comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序 diff --git "a/Java\347\233\270\345\205\263/Multithread/AQS.md" "b/Java\347\233\270\345\205\263/Multithread/AQS.md" new file mode 100644 index 00000000000..487669e50d8 --- /dev/null +++ "b/Java\347\233\270\345\205\263/Multithread/AQS.md" @@ -0,0 +1,475 @@ + +**目录:** + + +- [1 AQS 简单介绍](#1-aqs-简单介绍) +- [2 AQS 原理](#2-aqs-原理) + - [2.1 AQS 原理概览](#21-aqs-原理概览) + - [2.2 AQS 对资源的共享方式](#22-aqs-对资源的共享方式) + - [2.3 AQS底层使用了模板方法模式](#23-aqs底层使用了模板方法模式) +- [3 Semaphore\(信号量\)-允许多个线程同时访问](#3-semaphore信号量-允许多个线程同时访问) +- [4 CountDownLatch (倒计时器)](#4-countdownlatch-倒计时器) + - [4.1 CountDownLatch 的三种典型用法](#41-countdownlatch-的三种典型用法) + - [4.2 CountDownLatch 的使用示例](#42-countdownlatch-的使用示例) + - [4.3 CountDownLatch 的不足](#43-countdownlatch-的不足) + - [4.4 CountDownLatch相常见面试题:](#44-countdownlatch相常见面试题) +- [5 CyclicBarrier\(循环栅栏\)](#5-cyclicbarrier循环栅栏) + - [5.1 CyclicBarrier 的应用场景](#51-cyclicbarrier-的应用场景) + - [5.2 CyclicBarrier 的使用示例](#52-cyclicbarrier-的使用示例) + - [5.3 CyclicBarrier和CountDownLatch的区别](#53-cyclicbarrier和countdownlatch的区别) +- [6 ReentrantLock 和 ReentrantReadWriteLock](#6-reentrantlock-和-reentrantreadwritelock) + + + +> 常见问题:AQS原理?;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么?用过Semaphore吗? + +**本节思维导图:** + +![并发编程面试必备:AQS 原理以及 AQS 同步组件总结](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-31/61115865.jpg) + + +### 1 AQS 简单介绍 +AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 + +![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS.png) + +AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 + +### 2 AQS 原理 + +> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。 + +下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。 + +#### 2.1 AQS 原理概览 + +**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** + +> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 + +看个AQS(AbstractQueuedSynchronizer)原理图: + + +![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/CLH.png) + +AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 + +```java +private volatile int state;//共享变量,使用volatile修饰保证线程可见性 +``` + +状态信息通过protected类型的getState,setState,compareAndSetState进行操作 + +```java + +//返回同步状态的当前值 +protected final int getState() { + return state; +} + // 设置同步状态的值 +protected final void setState(int newState) { + state = newState; +} +//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) +protected final boolean compareAndSetState(int expect, int update) { + return unsafe.compareAndSwapInt(this, stateOffset, expect, update); +} +``` + +#### 2.2 AQS 对资源的共享方式 + +**AQS定义两种资源共享方式** + +- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: + - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 + - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 +- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 + +ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 + +不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。 + +#### 2.3 AQS底层使用了模板方法模式 + +同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): + +1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) +2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 + +这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。 + +> 模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票`buyTicket()`->安检`securityCheck()`->乘坐某某工具回家`ride()`->到达目的地`arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了`ride()`方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。 + +**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:** + +```java +isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 +tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 +tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 +tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 +tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 + +``` + +默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 + +以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 + +再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 + +一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 + +推荐两篇 AQS 原理和相关源码分析的文章: + +- http://www.cnblogs.com/waterystone/p/4920797.html +- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html + + + +### 3 Semaphore(信号量)-允许多个线程同时访问 + +**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。**示例代码如下: + +```java +/** + * + * @author Snailclimb + * @date 2018年9月30日 + * @Description: 需要一次性拿一个许可的情况 + */ +public class SemaphoreExample1 { + // 请求的数量 + private static final int threadCount = 550; + + public static void main(String[] args) throws InterruptedException { + // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) + ExecutorService threadPool = Executors.newFixedThreadPool(300); + // 一次只能允许执行的线程数量。 + final Semaphore semaphore = new Semaphore(20); + + for (int i = 0; i < threadCount; i++) { + final int threadnum = i; + threadPool.execute(() -> {// Lambda 表达式的运用 + try { + semaphore.acquire();// 获取一个许可,所以可运行线程数量为20/1=20 + test(threadnum); + semaphore.release();// 释放一个许可 + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + }); + } + threadPool.shutdown(); + System.out.println("finish"); + } + + public static void test(int threadnum) throws InterruptedException { + Thread.sleep(1000);// 模拟请求的耗时操作 + System.out.println("threadnum:" + threadnum); + Thread.sleep(1000);// 模拟请求的耗时操作 + } +} +``` + +执行 `acquire` 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 `release` 方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。 + +当然一次也可以一次拿取和释放多个许可,不过一般没有必要这样做: + +```java + semaphore.acquire(5);// 获取5个许可,所以可运行线程数量为20/5=4 + test(threadnum); + semaphore.release(5);// 获取5个许可,所以可运行线程数量为20/5=4 +``` + +除了 `acquire`方法之外,另一个比较常用的与之对应的方法是`tryAcquire`方法,该方法如果获取不到许可就立即返回false。 + + +Semaphore 有两种模式,公平模式和非公平模式。 + +- **公平模式:** 调用acquire的顺序就是获取许可证的顺序,遵循FIFO; +- **非公平模式:** 抢占式的。 + +**Semaphore 对应的两个构造方法如下:** + +```java + public Semaphore(int permits) { + sync = new NonfairSync(permits); + } + + public Semaphore(int permits, boolean fair) { + sync = fair ? new FairSync(permits) : new NonfairSync(permits); + } +``` +**这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。** + +由于篇幅问题,如果对 Semaphore 源码感兴趣的朋友可以看下面这篇文章: + +- https://blog.csdn.net/qq_19431333/article/details/70212663 + +### 4 CountDownLatch (倒计时器) + +CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。 + +#### 4.1 CountDownLatch 的三种典型用法 + +①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :`new CountDownLatch(n) `,每当一个任务线程执行完毕,就将计数器减1 `countdownlatch.countDown()`,当计数器的值变为0时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 + +②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1) `,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。 + +③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 + +#### 4.2 CountDownLatch 的使用示例 + +```java +/** + * + * @author SnailClimb + * @date 2018年10月1日 + * @Description: CountDownLatch 使用方法示例 + */ +public class CountDownLatchExample1 { + // 请求的数量 + private static final int threadCount = 550; + + public static void main(String[] args) throws InterruptedException { + // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) + ExecutorService threadPool = Executors.newFixedThreadPool(300); + final CountDownLatch countDownLatch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + final int threadnum = i; + threadPool.execute(() -> {// Lambda 表达式的运用 + try { + test(threadnum); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + countDownLatch.countDown();// 表示一个请求已经被完成 + } + + }); + } + countDownLatch.await(); + threadPool.shutdown(); + System.out.println("finish"); + } + + public static void test(int threadnum) throws InterruptedException { + Thread.sleep(1000);// 模拟请求的耗时操作 + System.out.println("threadnum:" + threadnum); + Thread.sleep(1000);// 模拟请求的耗时操作 + } +} + +``` +上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行`System.out.println("finish");`。 + +与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。 + +其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。 + +#### 4.3 CountDownLatch 的不足 + +CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。 + +#### 4.4 CountDownLatch相常见面试题: + +解释一下CountDownLatch概念? + +CountDownLatch 和CyclicBarrier的不同之处? + +给出一些CountDownLatch使用的例子? + +CountDownLatch 类中主要的方法? + +### 5 CyclicBarrier(循环栅栏) + +CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。 + +CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 + +#### 5.1 CyclicBarrier 的应用场景 + +CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。 + +#### 5.2 CyclicBarrier 的使用示例 + +示例1: + +```java +/** + * + * @author Snailclimb + * @date 2018年10月1日 + * @Description: 测试 CyclicBarrier 类中带参数的 await() 方法 + */ +public class CyclicBarrierExample2 { + // 请求的数量 + private static final int threadCount = 550; + // 需要同步的线程数量 + private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5); + + public static void main(String[] args) throws InterruptedException { + // 创建线程池 + ExecutorService threadPool = Executors.newFixedThreadPool(10); + + for (int i = 0; i < threadCount; i++) { + final int threadNum = i; + Thread.sleep(1000); + threadPool.execute(() -> { + try { + test(threadNum); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BrokenBarrierException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + } + threadPool.shutdown(); + } + + public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { + System.out.println("threadnum:" + threadnum + "is ready"); + try { + cyclicBarrier.await(2000, TimeUnit.MILLISECONDS); + } catch (Exception e) { + System.out.println("-----CyclicBarrierException------"); + } + System.out.println("threadnum:" + threadnum + "is finish"); + } + +} +``` + +运行结果,如下: + +``` +threadnum:0is ready +threadnum:1is ready +threadnum:2is ready +threadnum:3is ready +threadnum:4is ready +threadnum:4is finish +threadnum:0is finish +threadnum:1is finish +threadnum:2is finish +threadnum:3is finish +threadnum:5is ready +threadnum:6is ready +threadnum:7is ready +threadnum:8is ready +threadnum:9is ready +threadnum:9is finish +threadnum:5is finish +threadnum:8is finish +threadnum:7is finish +threadnum:6is finish +...... +``` +可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, `await`方法之后的方法才被执行。 + +另外,CyclicBarrier还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`,用于在线程到达屏障时,优先执行`barrierAction`,方便处理更复杂的业务场景。示例代码如下: + +```java +/** + * + * @author SnailClimb + * @date 2018年10月1日 + * @Description: 新建 CyclicBarrier 的时候指定一个 Runnable + */ +public class CyclicBarrierExample3 { + // 请求的数量 + private static final int threadCount = 550; + // 需要同步的线程数量 + private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { + System.out.println("------当线程数达到之后,优先执行------"); + }); + + public static void main(String[] args) throws InterruptedException { + // 创建线程池 + ExecutorService threadPool = Executors.newFixedThreadPool(10); + + for (int i = 0; i < threadCount; i++) { + final int threadNum = i; + Thread.sleep(1000); + threadPool.execute(() -> { + try { + test(threadNum); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BrokenBarrierException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + } + threadPool.shutdown(); + } + + public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { + System.out.println("threadnum:" + threadnum + "is ready"); + cyclicBarrier.await(); + System.out.println("threadnum:" + threadnum + "is finish"); + } + +} +``` + +运行结果,如下: + +``` +threadnum:0is ready +threadnum:1is ready +threadnum:2is ready +threadnum:3is ready +threadnum:4is ready +------当线程数达到之后,优先执行------ +threadnum:4is finish +threadnum:0is finish +threadnum:2is finish +threadnum:1is finish +threadnum:3is finish +threadnum:5is ready +threadnum:6is ready +threadnum:7is ready +threadnum:8is ready +threadnum:9is ready +------当线程数达到之后,优先执行------ +threadnum:9is finish +threadnum:5is finish +threadnum:6is finish +threadnum:8is finish +threadnum:7is finish +...... +``` +#### 5.3 CyclicBarrier和CountDownLatch的区别 + +CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看,javadoc是这么描述它们的: + +> CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;) +> CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。) + +对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。 + +CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。 + +![CyclicBarrier和CountDownLatch的区别](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS333.png) + +CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文章: + +- https://blog.csdn.net/u010185262/article/details/54692886 +- https://blog.csdn.net/tolcf/article/details/50925145?utm_source=blogxgwz0 + +### 6 ReentrantLock 和 ReentrantReadWriteLock + +ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。 + +由于篇幅问题,关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。 + +- [ReentrantLock 和 ReentrantReadWriteLock](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd) diff --git "a/Java\347\233\270\345\205\263/Multithread/Atomic.md" "b/Java\347\233\270\345\205\263/Multithread/Atomic.md" new file mode 100644 index 00000000000..5c794055b0c --- /dev/null +++ "b/Java\347\233\270\345\205\263/Multithread/Atomic.md" @@ -0,0 +1,337 @@ +> 个人觉得这一节掌握基本的使用即可! + +**本节思维导图:** + +![](https://user-gold-cdn.xitu.io/2018/10/30/166c58b785368234?w=1200&h=657&f=png&s=49615) + +### 1 Atomic 原子类介绍 + +Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 + +所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 + +并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 + +![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) + +根据操作的数据类型,可以将JUC包中的原子类分为4类 + +**基本类型** + +使用原子的方式更新基本类型 + +- AtomicInteger:整形原子类 +- AtomicLong:长整型原子类 +- AtomicBoolean :布尔型原子类 + +**数组类型** + +使用原子的方式更新数组里的某个元素 + + +- AtomicIntegerArray:整形数组原子类 +- AtomicLongArray:长整形数组原子类 +- AtomicReferenceArray :引用类型数组原子类 + +**引用类型** + +- AtomicReference:引用类型原子类 +- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicMarkableReference :原子更新带有标记位的引用类型 + +**对象的属性修改类型** + +- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 +- AtomicLongFieldUpdater:原子更新长整形字段的更新器 +- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + +下面我们来详细介绍一下这些原子类。 + +### 2 基本类型原子类 + +#### 2.1 基本类型原子类介绍 + +使用原子的方式更新基本类型 + +- AtomicInteger:整形原子类 +- AtomicLong:长整型原子类 +- AtomicBoolean :布尔型原子类 + +上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。 + + **AtomicInteger 类常用方法** + +```java +public final int get() //获取当前的值 +public final int getAndSet(int newValue)//获取当前的值,并设置新的值 +public final int getAndIncrement()//获取当前的值,并自增 +public final int getAndDecrement() //获取当前的值,并自减 +public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 +boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) +public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 +``` + +#### 2.2 AtomicInteger 常见方法使用 + +```java +import java.util.concurrent.atomic.AtomicInteger; + +public class AtomicIntegerTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int temvalue = 0; + AtomicInteger i = new AtomicInteger(0); + temvalue = i.getAndSet(3); + System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3 + temvalue = i.getAndIncrement(); + System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4 + temvalue = i.getAndAdd(5); + System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9 + } + +} +``` + +#### 2.3 基本数据类型原子类的优势 + +通过一个简单例子带大家看一下基本数据类型原子类的优势 + +**①多线程环境不使用原子类保证线程安全(基本数据类型)** + +```java +class Test { + private volatile int count = 0; + //若要线程安全执行执行count++,需要加锁 + public synchronized void increment() { + count++; + } + + public int getCount() { + return count; + } +} +``` +**②多线程环境使用原子类保证线程安全(基本数据类型)** + +```java +class Test2 { + private AtomicInteger count = new AtomicInteger(); + + public void increment() { + count.incrementAndGet(); + } + //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。 + public int getCount() { + return count.get(); + } +} + +``` +#### 2.4 AtomicInteger 线程安全原理简单分析 + +AtomicInteger 类的部分源码: + +```java + // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; +``` + +AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 + +CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 + + +### 3 数组类型原子类 + +#### 3.1 数组类型原子类介绍 + +使用原子的方式更新数组里的某个元素 + + +- AtomicIntegerArray:整形数组原子类 +- AtomicLongArray:长整形数组原子类 +- AtomicReferenceArray :引用类型数组原子类 + +上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。 + +**AtomicIntegerArray 类常用方法** + +```java +public final int get(int i) //获取 index=i 位置元素的值 +public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue +public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增 +public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减 +public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值 +boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update) +public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 +``` +#### 3.2 AtomicIntegerArray 常见方法使用 + +```java + +import java.util.concurrent.atomic.AtomicIntegerArray; + +public class AtomicIntegerArrayTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int temvalue = 0; + int[] nums = { 1, 2, 3, 4, 5, 6 }; + AtomicIntegerArray i = new AtomicIntegerArray(nums); + for (int j = 0; j < nums.length; j++) { + System.out.println(i.get(j)); + } + temvalue = i.getAndSet(0, 2); + System.out.println("temvalue:" + temvalue + "; i:" + i); + temvalue = i.getAndIncrement(0); + System.out.println("temvalue:" + temvalue + "; i:" + i); + temvalue = i.getAndAdd(0, 5); + System.out.println("temvalue:" + temvalue + "; i:" + i); + } + +} +``` + +### 4 引用类型原子类 + +#### 4.1 引用类型原子类介绍 + +基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。 + +- AtomicReference:引用类型原子类 +- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicMarkableReference :原子更新带有标记位的引用类型 + +上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。 + +#### 4.2 AtomicReference 类使用示例 + +```java +import java.util.concurrent.atomic.AtomicReference; + +public class AtomicReferenceTest { + + public static void main(String[] args) { + AtomicReference ar = new AtomicReference(); + Person person = new Person("SnailClimb", 22); + ar.set(person); + Person updatePerson = new Person("Daisy", 20); + ar.compareAndSet(person, updatePerson); + + System.out.println(ar.get().getName()); + System.out.println(ar.get().getAge()); + } +} + +class Person { + private String name; + private int age; + + public Person(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + +} +``` +上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下: + +``` +Daisy +20 +``` + + +### 5 对象的属性修改类型原子类 + +#### 5.1 对象的属性修改类型原子类介绍 + +如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。 + +- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 +- AtomicLongFieldUpdater:原子更新长整形字段的更新器 +- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + +要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。 + +上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。 + +#### 5.2 AtomicIntegerFieldUpdater 类使用示例 + +```java +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +public class AtomicIntegerFieldUpdaterTest { + public static void main(String[] args) { + AtomicIntegerFieldUpdater a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); + + User user = new User("Java", 22); + System.out.println(a.getAndIncrement(user));// 22 + System.out.println(a.get(user));// 23 + } +} + +class User { + private String name; + public volatile int age; + + public User(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + +} +``` + +输出结果: + +``` +22 +23 +``` + diff --git "a/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" "b/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 00000000000..10d6177b040 --- /dev/null +++ "b/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,429 @@ + + + +# 一 面试中关于 synchronized 关键字的 5 连击 + +### 1.1 说一说自己对于 synchronized 关键字的了解 + +synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 + +另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 + + +### 1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 + +**synchronized关键字最主要的三种使用方式:** + +- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** +- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 +- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! + +下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 + +面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” + + + +**双重校验锁实现对象单例(线程安全)** + +```java +public class Singleton { + + private volatile static Singleton uniqueInstance; + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + //先判断对象是否已经实例过,没有实例化过才进入加锁代码 + if (uniqueInstance == null) { + //类对象加锁 + synchronized (Singleton.class) { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } +} +``` +另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 + +uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: + +1. 为 uniqueInstance 分配内存空间 +2. 初始化 uniqueInstance +3. 将 uniqueInstance 指向分配的内存地址 + +但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 + +使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 + +### 1.3 讲一下 synchronized 关键字的底层原理 + +**synchronized 关键字底层原理属于 JVM 层面。** + +**① synchronized 同步语句块的情况** + +```java +public class SynchronizedDemo { + public void method() { + synchronized (this) { + System.out.println("synchronized 代码块"); + } + } +} + +``` + +通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 + +![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add616a292bcf?w=917&h=633&f=png&s=21863) + +从上面我们可以看出: + +**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 + +**② synchronized 修饰方法的的情况** + +```java +public class SynchronizedDemo2 { + public synchronized void method() { + System.out.println("synchronized 方法"); + } +} + +``` + +![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add6169fc206d?w=875&h=421&f=png&s=16114) + +synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 + + +### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 + +JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 + +锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 + +关于这几种优化的详细信息可以查看:[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd) + +### 1.5 谈谈 synchronized和ReenTrantLock 的区别 + + +**① 两者都是可重入锁** + +两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 + +**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** + +synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 + +**③ ReenTrantLock 比 synchronized 增加了一些高级功能** + +相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** + +- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 +- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 +- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 + +如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 + +**④ 性能已不是选择标准** + +# 二 面试中关于线程池的 4 连击 + +### 2.1 讲一下Java内存模型 + + +在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。 + +![数据的不一致](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4423ba2?w=273&h=166&f=jpeg&s=7268) + +要解决这个问题,就需要把变量声明为 **volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。 + +说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。 + +![volatile关键字的可见性](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4b9f501?w=474&h=238&f=jpeg&s=9942) + + +### 2.2 说说 synchronized 关键字和 volatile 关键字的区别 + + synchronized关键字和volatile关键字比较 + +- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。 +- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞** +- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。** +- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。** + + +# 三 面试中关于 线程池的 2 连击 + + +### 3.1 为什么要用线程池? + +线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 + +这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处: + +- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 +- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 +- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 + + +### 3.2 实现Runnable接口和Callable接口的区别 + +如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。 + + **备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callable(Runnable task)`或`Executors.callable(Runnable task,Object resule)`)。 + +### 3.3 执行execute()方法和submit()方法的区别是什么呢? + + 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** + + 2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功**,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 + + +### 3.4 如何创建线程池 + +《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险 + +> Executors 返回线程池对象的弊端如下: +> +> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 +> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 + +**方式一:通过构造方法实现** +![通过构造方法实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baac923e9?w=925&h=158&f=jpeg&s=29190) +**方式二:通过Executor 框架的工具类Executors来实现** +我们可以创建三种类型的ThreadPoolExecutor: + +- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 +- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 +- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 + +对应Executors工具类中的方法如图所示: +![通过Executor 框架的工具类Executors来实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baa9ca5e9?w=645&h=222&f=jpeg&s=31710) + + +# 四 面试中关于 Atomic 原子类的 4 连击 + +### 4.1 介绍一下Atomic 原子类 + +Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 + +所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 + + +并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 + +![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) + +### 4.2 JUC 包中的原子类是哪4类? + +**基本类型** + +使用原子的方式更新基本类型 + +- AtomicInteger:整形原子类 +- AtomicLong:长整型原子类 +- AtomicBoolean :布尔型原子类 + +**数组类型** + +使用原子的方式更新数组里的某个元素 + + +- AtomicIntegerArray:整形数组原子类 +- AtomicLongArray:长整形数组原子类 +- AtomicReferenceArray :引用类型数组原子类 + +**引用类型** + +- AtomicReference:引用类型原子类 +- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicMarkableReference :原子更新带有标记位的引用类型 + +**对象的属性修改类型** + +- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 +- AtomicLongFieldUpdater:原子更新长整形字段的更新器 +- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + + +### 4.3 讲讲 AtomicInteger 的使用 + + **AtomicInteger 类常用方法** + +```java +public final int get() //获取当前的值 +public final int getAndSet(int newValue)//获取当前的值,并设置新的值 +public final int getAndIncrement()//获取当前的值,并自增 +public final int getAndDecrement() //获取当前的值,并自减 +public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 +boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) +public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 +``` + + **AtomicInteger 类的使用示例** + +使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。 +```java +class AtomicIntegerTest { + private AtomicInteger count = new AtomicInteger(); + //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 + public void increment() { + count.incrementAndGet(); + } + + public int getCount() { + return count.get(); + } +} + +``` + +### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理 + +AtomicInteger 线程安全原理简单分析 + +AtomicInteger 类的部分源码: + +```java + // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; +``` + +AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 + +CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 + +关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) + +# 五 AQS + +### 5.1 AQS 介绍 + +AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 + +![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bb575d4a690?w=317&h=338&f=png&s=14122) + +AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 + +### 5.2 AQS 原理分析 + +AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。 + +> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。 + +下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。 + +#### 5.2.1 AQS 原理概览 + + + +**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** + +> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 + +看个AQS(AbstractQueuedSynchronizer)原理图: + + +![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bbe4a9c5ae7?w=852&h=401&f=png&s=21797) + +AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 + +```java +private volatile int state;//共享变量,使用volatile修饰保证线程可见性 +``` + +状态信息通过procted类型的getState,setState,compareAndSetState进行操作 + +```java + +//返回同步状态的当前值 +protected final int getState() { + return state; +} + // 设置同步状态的值 +protected final void setState(int newState) { + state = newState; +} +//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) +protected final boolean compareAndSetState(int expect, int update) { + return unsafe.compareAndSwapInt(this, stateOffset, expect, update); +} +``` + +#### 5.2.2 AQS 对资源的共享方式 + +**AQS定义两种资源共享方式** + +- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: + - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 + - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 +- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 + +ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 + +不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。 + +#### 5.2.3 AQS底层使用了模板方法模式 + +同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): + +1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) +2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 + +这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。 + +**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:** + +```java +isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 +tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 +tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 +tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 +tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 + +``` + +默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 + +以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 + +再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 + +一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 + +推荐两篇 AQS 原理和相关源码分析的文章: + +- http://www.cnblogs.com/waterystone/p/4920797.html +- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html + +### 5.3 AQS 组件总结 + +- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。 +- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。 +- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 + +关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) + +# Reference + +- 《深入理解 Java 虚拟机》 +- 《实战 Java 高并发程序设计》 +- 《Java并发编程的艺术》 +- http://www.cnblogs.com/waterystone/p/4920797.html +- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html diff --git "a/Java\347\233\270\345\205\263/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" "b/Java\347\233\270\345\205\263/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..70164c135e4 --- /dev/null +++ "b/Java\347\233\270\345\205\263/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" @@ -0,0 +1,223 @@ + + + +- [一 JDK 提供的并发容器总结](#一-jdk-提供的并发容器总结) +- [二 ConcurrentHashMap](#二-concurrenthashmap) +- [三 CopyOnWriteArrayList](#三-copyonwritearraylist) + - [3.1 CopyOnWriteArrayList 简介](#31-copyonwritearraylist-简介) + - [3.2 CopyOnWriteArrayList 是如何做到的?](#32-copyonwritearraylist-是如何做到的?) + - [3.3 CopyOnWriteArrayList 读取和写入源码简单分析](#33-copyonwritearraylist-读取和写入源码简单分析) + - [3.3.1 CopyOnWriteArrayList 读取操作的实现](#331-copyonwritearraylist-读取操作的实现) + - [3.3.2 CopyOnWriteArrayList 写入操作的实现](#332-copyonwritearraylist-写入操作的实现) +- [四 ConcurrentLinkedQueue](#四-concurrentlinkedqueue) +- [五 BlockingQueue](#五-blockingqueue) + - [5.1 BlockingQueue 简单介绍](#51-blockingqueue-简单介绍) + - [5.2 ArrayBlockingQueue](#52-arrayblockingqueue) + - [5.3 LinkedBlockingQueue](#53-linkedblockingqueue) + - [5.4 PriorityBlockingQueue](#54-priorityblockingqueue) +- [六 ConcurrentSkipListMap](#六-concurrentskiplistmap) +- [七 参考](#七-参考) + + + +## 一 JDK 提供的并发容器总结 + +JDK提供的这些容器大部分在 `java.util.concurrent` 包中。 + + +- **ConcurrentHashMap:** 线程安全的HashMap +- **CopyOnWriteArrayList:** 线程安全的List,在读多写少的场合性能非常好,远远好于Vector. +- **ConcurrentLinkedQueue:**高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。 +- **BlockingQueue:** 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。 +- **ConcurrentSkipListMap:** 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。 + +## 二 ConcurrentHashMap + +我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 `Collections.synchronizedMap()` 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。 + +所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。 + +关于 ConcurrentHashMap 相关问题,我在 [《这几道Java集合框架面试题几乎必问》](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题: + +- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB) +- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0) + + + +## 三 CopyOnWriteArrayList + +### 3.1 CopyOnWriteArrayList 简介 + +```java +public class CopyOnWriteArrayList +extends Object +implements List, RandomAccess, Cloneable, Serializable +``` + +在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。 + +这和我们之前在多线程章节讲过 `ReentrantReadWriteLock` 读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 `CopyOnWriteArrayList` 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,`CopyOnWriteArrayList` 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。**那它是怎么做的呢?** + +### 3.2 CopyOnWriteArrayList 是如何做到的? + + `CopyOnWriteArrayList` 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。 + +从 `CopyOnWriteArrayList` 的名字就能看出`CopyOnWriteArrayList` 是满足`CopyOnWrite` 的ArrayList,所谓`CopyOnWrite` 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。 + +### 3.3 CopyOnWriteArrayList 读取和写入源码简单分析 + +#### 3.3.1 CopyOnWriteArrayList 读取操作的实现 + +读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全。 + +```java + /** The array, accessed only via getArray/setArray. */ + private transient volatile Object[] array; + public E get(int index) { + return get(getArray(), index); + } + @SuppressWarnings("unchecked") + private E get(Object[] a, int index) { + return (E) a[index]; + } + final Object[] getArray() { + return array; + } + +``` + +#### 3.3.2 CopyOnWriteArrayList 写入操作的实现 + +CopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁,保证了同步,避免了多线程写的时候会 copy 出多个副本出来。 + +```java + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(E e) { + final ReentrantLock lock = this.lock; + lock.lock();//加锁 + try { + Object[] elements = getArray(); + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝新数组 + newElements[len] = e; + setArray(newElements); + return true; + } finally { + lock.unlock();//释放锁 + } + } +``` + +## 四 ConcurrentLinkedQueue + +Java提供的线程安全的 Queue 可以分为**阻塞队列**和**非阻塞队列**,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 **阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。** + +从名字可以看出,`ConcurrentLinkedQueue`这个队列使用链表作为其数据结构.ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能,是因为其内部复杂的实现。 + +ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。 + +ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。 + +## 五 BlockingQueue + +### 5.1 BlockingQueue 简单介绍 + +上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。 + +BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类: + +![BlockingQueue 的实现类](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/51622268.jpg) + +**下面主要介绍一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,这三个 BlockingQueue 的实现类。** + +### 5.2 ArrayBlockingQueue + +**ArrayBlockingQueue** 是 BlockingQueue 接口的有界队列实现类,底层采用**数组**来实现。ArrayBlockingQueue一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。 + +ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码: + +```java +private static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(10,true); +``` + +### 5.3 LinkedBlockingQueue + +**LinkedBlockingQueue** 底层基于**单向链表**实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足FIFO的特性,与ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于Integer.MAX_VALUE。 + +**相关构造方法:** + +```java + /** + *某种意义上的无界队列 + * Creates a {@code LinkedBlockingQueue} with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public LinkedBlockingQueue() { + this(Integer.MAX_VALUE); + } + + /** + *有界队列 + * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. + * + * @param capacity the capacity of this queue + * @throws IllegalArgumentException if {@code capacity} is not greater + * than zero + */ + public LinkedBlockingQueue(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException(); + this.capacity = capacity; + last = head = new Node(null); + } +``` + +### 5.4 PriorityBlockingQueue + +**PriorityBlockingQueue** 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 `compareTo()` 方法来指定元素排序规则,或者初始化时通过构造器参数 `Comparator` 来指定排序规则。 + +PriorityBlockingQueue 并发控制采用的是 **ReentrantLock**,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,**如果空间不够的话会自动扩容**)。 + +简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。 + +**推荐文章:** + +《解读 Java 并发队列 BlockingQueue》 + +[https://javadoop.com/post/java-concurrent-queue](https://javadoop.com/post/java-concurrent-queue) + +## 六 ConcurrentSkipListMap + +下面这部分内容参考了极客时间专栏[《数据结构与算法之美》](https://time.geekbang.org/column/intro/126?code=zl3GYeAsRI4rEJIBNu5B/km7LSZsPDlGWQEpAYw5Vu0=&utm_term=SPoster)以及《实战Java高并发程序设计》。 + +**为了引出ConcurrentSkipListMap,先带着大家简单理解一下跳表。** + +对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 **O(logn)** 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。 + +跳表的本质是同时维护了多个链表,并且链表是分层的, + +![2级索引跳表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/93666217.jpg) + +最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。 + +跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素18。 + +![在跳表中查找元素18](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/32005738.jpg) + +查找18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。 + +从上面很容易看出,**跳表是一种利用空间换时间的算法。** + +使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。 + + + +## 七 参考 + +- 《实战Java高并发程序设计》 +- https://javadoop.com/post/java-concurrent-queue +- https://juejin.im/post/5aeebd02518825672f19c546 diff --git "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" "b/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" index d891cc3346e..2c8a917f710 100644 --- "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" +++ "b/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" @@ -1,3 +1,26 @@ + + +- [final,static,this,super 关键字总结](#finalstaticthissuper-关键字总结) + - [final 关键字](#final-关键字) + - [static 关键字](#static-关键字) + - [this 关键字](#this-关键字) + - [super 关键字](#super-关键字) + - [参考](#参考) +- [static 关键字详解](#static-关键字详解) + - [static 关键字主要有以下四种使用场景](#static-关键字主要有以下四种使用场景) + - [修饰成员变量和成员方法\(常用\)](#修饰成员变量和成员方法常用) + - [静态代码块](#静态代码块) + - [静态内部类](#静态内部类) + - [静态导包](#静态导包) + - [补充内容](#补充内容) + - [静态方法与非静态方法](#静态方法与非静态方法) + - [static{}静态代码块与{}非静态代码块\(构造代码块\)](#static静态代码块与非静态代码块构造代码块) + - [参考](#参考-1) + + + +# final,static,this,super 关键字总结 + ## final 关键字 **final关键字主要用在三个地方:变量、方法、类。** @@ -14,7 +37,7 @@ 1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` 2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. -3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 +3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 ## this 关键字 @@ -70,7 +93,7 @@ public class Sub extends Super { **使用 this 和 super 要注意的问题:** - super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用本类中的其他构造方法时,也要放在首行。 -- this、super不能用在static方法中。 +- this、super不能用在static方法中。 **简单解释一下:** @@ -82,3 +105,244 @@ public class Sub extends Super { - https://www.codejava.net/java-core/the-java-language/java-keywords - https://blog.csdn.net/u013393958/article/details/79881037 + +# static 关键字详解 + +## static 关键字主要有以下四种使用场景 + +1. 修饰成员变量和成员方法 +2. 静态代码块 +3. 修饰类(只能修饰内部类) +4. 静态导包(用来导入类中的静态资源,1.5之后的新特性) + +### 修饰成员变量和成员方法(常用) + +被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。 + +方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 + + HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 + + + +调用格式: + +- 类名.静态变量名 +- 类名.静态方法名() + +如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。 + +测试方法: + +```java +public class StaticBean { + + String name; + 静态变量 + static int age; + + public StaticBean(String name) { + this.name = name; + } + 静态方法 + static void SayHello() { + System.out.println(Hello i am java); + } + @Override + public String toString() { + return StaticBean{ + + name=' + name + ''' + age + age + + '}'; + } +} +``` + +```java +public class StaticDemo { + + public static void main(String[] args) { + StaticBean staticBean = new StaticBean(1); + StaticBean staticBean2 = new StaticBean(2); + StaticBean staticBean3 = new StaticBean(3); + StaticBean staticBean4 = new StaticBean(4); + StaticBean.age = 33; + StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33} + System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4); + StaticBean.SayHello();Hello i am java + } + +} +``` + + +### 静态代码块 + +静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次. + +静态代码块的格式是 + +``` +static { +语句体; +} +``` + + +一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/88531075.jpg) + +静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. + + +### 静态内部类 + +静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着: + +1. 它的创建是不需要依赖外围类的创建。 +2. 它不能使用任何外围类的非static成员变量和方法。 + + +Example(静态内部类实现单例模式) + +```java +public class Singleton { + + 声明为 private 避免调用默认构造方法创建对象 + private Singleton() { + } + + 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getUniqueInstance() { + return SingletonHolder.INSTANCE; + } +} +``` + +当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。 + +这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 + +### 静态导包 + +格式为:import static + +这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法 + +```java + + + Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 + 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可 + +import static java.lang.Math.; + + 换成import static java.lang.Math.max;具有一样的效果 + +public class Demo { + public static void main(String[] args) { + + int max = max(1,2); + System.out.println(max); + } +} + +``` + + +## 补充内容 + +### 静态方法与非静态方法 + +静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。 + +Example + +```java +class Foo { + int i; + public Foo(int i) { + this.i = i; + } + + public static String method1() { + return An example string that doesn't depend on i (an instance variable); + + } + + public int method2() { + return this.i + 1; Depends on i + } + +} +``` +你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();` + +总结: + +- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 +- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 + +### static{}静态代码块与{}非静态代码块(构造代码块) + +相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。 + +不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 + +一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. + +Example + +```java +public class Test { + public Test() { + System.out.print(默认构造方法!--); + } + + 非静态代码块 + { + System.out.print(非静态代码块!--); + } + 静态代码块 + static { + System.out.print(静态代码块!--); + } + + public static void test() { + System.out.print(静态方法中的内容! --); + { + System.out.print(静态方法中的代码块!--); + } + + } + public static void main(String[] args) { + + Test test = new Test(); + Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!-- + } +``` + +当执行 `Test.test();` 时输出: + +``` +静态代码块!--静态方法中的内容! --静态方法中的代码块!-- +``` + +当执行 `Test test = new Test();` 时输出: + +``` +静态代码块!--非静态代码块!--默认构造方法!-- +``` + + +非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。 + +### 参考 + +- httpsblog.csdn.netchen13579867831articledetails78995480 +- httpwww.cnblogs.comchenssyp3388487.html +- httpwww.cnblogs.comQian123p5713440.html diff --git "a/Java\347\233\270\345\205\263/static.md" "b/Java\347\233\270\345\205\263/static.md" deleted file mode 100644 index 713efff8eae..00000000000 --- "a/Java\347\233\270\345\205\263/static.md" +++ /dev/null @@ -1,241 +0,0 @@ - -# static 关键字 - -## static 关键字主要有以下四种使用场景 - -1. 修饰成员变量和成员方法 -2. 静态代码块 -3. 修饰类(只能修饰内部类) -4. 静态导包(用来导入类中的静态资源,1.5之后的新特性) - -### 修饰成员变量和成员方法(常用) - -被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。 - -方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 - - HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 - - - -调用格式: - -- 类名.静态变量名 -- 类名.静态方法名() - -如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被方法。 - -测试方法: - -```java -public class StaticBean { - - String name; - 静态变量 - static int age; - - public StaticBean(String name) { - this.name = name; - } - 静态方法 - static void SayHello() { - System.out.println(Hello i am java); - } - @Override - public String toString() { - return StaticBean{ + - name=' + name + ''' + age + age + - '}'; - } -} -``` - -```java -public class StaticDemo { - - public static void main(String[] args) { - StaticBean staticBean = new StaticBean(1); - StaticBean staticBean2 = new StaticBean(2); - StaticBean staticBean3 = new StaticBean(3); - StaticBean staticBean4 = new StaticBean(4); - StaticBean.age = 33; - StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33} - System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4); - StaticBean.SayHello();Hello i am java - } - -} -``` - - -### 静态代码块 - -静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次. - -静态代码块的格式是 - -``` -static { -语句体; -} -``` - - -一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/88531075.jpg) - -静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. - - -### 静态内部类 - -静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着: - -1. 它的创建是不需要依赖外围类的创建。 -2. 它不能使用任何外围类的非static成员变量和方法。 - - -Example(静态内部类实现单例模式) - -```java -public class Singleton { - - 声明为 private 避免调用默认构造方法创建对象 - private Singleton() { - } - - 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 - private static class SingletonHolder { - private static final Singleton INSTANCE = new Singleton(); - } - - public static Singleton getUniqueInstance() { - return SingletonHolder.INSTANCE; - } -} -``` - -当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。 - -这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 - -### 静态导包 - -格式为:import static - -这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法 - -```java - - - Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 - 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可 - -import static java.lang.Math.; - - 换成import static java.lang.Math.max;具有一样的效果 - -public class Demo { - public static void main(String[] args) { - - int max = max(1,2); - System.out.println(max); - } -} - -``` - - -## 补充内容 - -### 静态方法与非静态方法 - -静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。 - -Example - -```java -class Foo { - int i; - public Foo(int i) { - this.i = i; - } - - public static String method1() { - return An example string that doesn't depend on i (an instance variable); - - } - - public int method2() { - return this.i + 1; Depends on i - } - -} -``` -你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();` - -总结: - -- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 -- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 - -### static{}静态代码块与{}非静态代码块(构造代码块) - -相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。 - -不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 - -一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. - -Example - -```java -public class Test { - public Test() { - System.out.print(默认构造方法!--); - } - - 非静态代码块 - { - System.out.print(非静态代码块!--); - } - 静态代码块 - static { - System.out.print(静态代码块!--); - } - - public static void test() { - System.out.print(静态方法中的内容! --); - { - System.out.print(静态方法中的代码块!--); - } - - } - public static void main(String[] args) { - - Test test = new Test(); - Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!-- - } -``` - -当执行 `Test.test();` 时输出: - -``` -静态代码块!--静态方法中的内容! --静态方法中的代码块!-- -``` - -当执行 `Test test = new Test();` 时输出: - -``` -静态代码块!--非静态代码块!--默认构造方法!-- -``` - - -非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。 - -### 参考 - -- httpsblog.csdn.netchen13579867831articledetails78995480 -- httpwww.cnblogs.comchenssyp3388487.html -- httpwww.cnblogs.comQian123p5713440.html diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" index ddd844266b4..4f38f3a1261 100644 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" @@ -22,7 +22,7 @@ ## 2 运行时数据区域 Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 ![运行时数据区域](https://user-gold-cdn.xitu.io/2018/4/27/16306a34cd8a4354?w=513&h=404&f=png&s=132068) -这些组成部分一些事线程私有的,其他的则是线程共享的。 +这些组成部分一些是线程私有的,其他的则是线程共享的。 **线程私有的:** @@ -75,7 +75,7 @@ Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟 ### 2.4 堆 Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。** -Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:在细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** +Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** ![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946) @@ -159,9 +159,9 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 ### 3.2 对象的内存布局 -在 Hotspot 虚拟机中,对象在内存中的布局可以分为3快区域:**对象头**、**实例数据**和**对齐填充**。 +在 Hotspot 虚拟机中,对象在内存中的布局可以分为3块区域:**对象头**、**实例数据**和**对齐填充**。 -**Hotspot虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希吗、GC分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 +**Hotspot虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 **实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。 @@ -173,7 +173,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 1. **句柄:** 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; ![使用句柄](https://user-gold-cdn.xitu.io/2018/4/27/16306b9573968946?w=786&h=362&f=png&s=109201) -2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对像的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。 +2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。 ![使用直接指针](https://user-gold-cdn.xitu.io/2018/4/27/16306ba3a41b6b65?w=766&h=353&f=png&s=99172) @@ -203,7 +203,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 **2 String 类型的常量池比较特殊。它的主要使用方法有两种:** - 直接使用双引号声明出来的 String 对象会直接存储在常量池中。 -- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 +- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 ```java String s1 = new String("计算机"); @@ -211,7 +211,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 String s3 = "计算机"; System.out.println(s2);//计算机 System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象, - System.out.println(s3 == s2);//true,因为两个都是常量池中的String对 + System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象 ``` **3 String 字符串拼接** ```java diff --git "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" "b/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" index 649d44e1f4f..6ed7bdafb21 100644 --- "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" +++ "b/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" @@ -65,5 +65,5 @@ ### Java多线程学习(八)线程池与Executor 框架 -![本节思维导图](https://user-gold-cdn.xitu.io/2018/5/31/163b4379a605fa18?w=1560&h=752&f=png&s=56361) +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-14/86510659.jpg) diff --git "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" "b/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" index 21ed7047dd7..819d37faca2 100644 --- "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" +++ "b/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" @@ -20,7 +20,7 @@ ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg) -当需要排查各种 内存溢出问题、当垃圾收集称为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 +当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 @@ -32,7 +32,7 @@ Java 的自动内存管理主要是针对对象内存的回收和对象内存的 ![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946) -从上图可以看出堆内存的分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survior1 区+Survior2 区。值得注意的是,在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。 +从上图可以看出堆内存分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survivor1 区+Survivor2 区。值得注意的是,在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。 ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/89294547.jpg) @@ -42,7 +42,7 @@ Java 的自动内存管理主要是针对对象内存的回收和对象内存的 大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC.下面我们来进行实际测试以下。 -在测试之前我们先来看看 **Minor Gc和Full GC 有什么不同呢?** +在测试之前我们先来看看 **Minor GC和Full GC 有什么不同呢?** - **新生代GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。 - **老年代GC(Major GC/Full GC)**:指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。 @@ -65,7 +65,7 @@ public class GCTest { 添加的参数:`-XX:+PrintGCDetails` ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg) -运行结果: +运行结果(红色字体描述有误,应该是对应于JDK1.7的永久代): ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg) @@ -76,7 +76,7 @@ allocation2 = new byte[900*1024]; ``` ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg) -**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survior空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。可以执行如下代码验证: +**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survivor空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。可以执行如下代码验证: ```java public class GCTest { @@ -102,7 +102,7 @@ public class GCTest { 为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。 ### 1.3 长期存活的对象将进入老年代 -既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别那些对象应放在新生代,那些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 +既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 @@ -163,13 +163,13 @@ JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用 **2.软引用(SoftReference)** -如果一个对象只具有软引用,那就类似于**可有可物的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 +如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。 **3.弱引用(WeakReference)** -如果一个对象只具有弱引用,那就类似于**可有可物的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 +如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 @@ -230,15 +230,15 @@ JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用 ![复制算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg) ### 3.3 标记-整理算法 -根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。 +根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 ![标记-整理算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg) ### 3.4 分代收集算法 -当前虚拟机的垃圾手机都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 +当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 -**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清楚”或“标记-整理”算法进行垃圾收集。** +**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** **延伸面试问题:** HotSpot为什么要分为新生代和老年代? @@ -250,7 +250,7 @@ JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用 **如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。** -虽然我们对各个收集器进行比较,但并非了挑选出一个最好的收集器。因为知道现在位置还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。 +虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。 ### 4.1 Serial收集器 diff --git "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" index 32a78ea6780..c36bb237ee5 100644 --- "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -97,7 +97,7 @@ - **职责链模式:** [Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639) - + [责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html) - **命令模式:** @@ -108,9 +108,19 @@ - **迭代器模式:** - **中介者模式:** - **备忘录模式:** -- **观察者模式:** +- **观察者模式:** + +观察者模式也是非常常用的设计模式,下面这个博客简单介绍了观察者模式的简单定义、解决了一个什么问题,用一个气象站和气象看板的例子去描述一对多的关系中观察者模式的应用,并且还介绍了jdk内置的观察者模式接口。 + +[Java设计模式之观察者模式](https://zhanglijun1217.github.io/blog/2018/12/24/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E2%80%94%E2%80%94%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F-md/) + - **状态模式:** - **策略模式:** + +策略模式作为设计原则中开闭原则最典型的体现,也是经常使用的。下面这篇博客介绍了策略模式一般的组成部分和概念,并用了一个小demo去说明了策略模式的应用。 + +[java设计模式之策略模式](https://blog.csdn.net/zlj1217/article/details/81230077) + - **模板方法模式:** - **访问者模式:** diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" index f934d914d5f..18587f42392 100644 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" @@ -1,27 +1,38 @@ -> 本文是“最最最常见Java面试题总结”系列第三周的文章。 -> 主要内容: -> 1. Arraylist 与 LinkedList 异同 -> 2. ArrayList 与 Vector 区别 -> 3. HashMap的底层实现 -> 4. HashMap 和 Hashtable 的区别 -> 5. HashMap 的长度为什么是2的幂次方 -> 6. HashMap 多线程操作导致死循环问题 -> 7. HashSet 和 HashMap 区别 -> 8. ConcurrentHashMap 和 Hashtable 的区别 -> 9. ConcurrentHashMap线程安全的具体实现方式/底层具体实现 -> 10. 集合框架底层数据结构总结 + + +- [Arraylist 与 LinkedList 异同](#arraylist-与-linkedlist-异同) + - [补充:数据结构基础之双向链表](#补充:数据结构基础之双向链表) +- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别) +- [HashMap的底层实现](#hashmap的底层实现) + - [JDK1.8之前](#jdk18之前) + - [JDK1.8之后](#jdk18之后) +- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别) +- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方) +- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题) +- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别) +- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别) +- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现) + - [JDK1.7(上面有示意图)](#jdk17(上面有示意图)) + - [JDK1.8 (上面有示意图)](#jdk18-(上面有示意图)) +- [集合框架底层数据结构总结](#集合框架底层数据结构总结) + - [Collection](#collection) + - [1. List](#1-list) + - [2. Set](#2-set) + - [Map](#map) + - [推荐阅读:](#推荐阅读:) + + ## Arraylist 与 LinkedList 异同 - **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; -- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构; +- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html) - **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** - **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 - **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 - -**补充内容:RandomAccess接口** +-**6.补充内容:RandomAccess接口** ```java public interface RandomAccess { @@ -42,12 +53,13 @@ public interface RandomAccess { } ``` -ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。实际上链表也是支持的,不过需要遍历到特定位置才行,时间复杂度为 O(n)。所以,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的! - +ArrayList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArrayList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArrayList 实现 RandomAccess 接口才具有快速随机访问功能的! + + **下面再总结一下 list 的遍历方式选择:** -- 实现了RadmoAcces接口的list,优先选择普通for循环 ,其次foreach, -- 未实现RadmoAcces接口的ist, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环 +- 实现了RandomAccess接口的list,优先选择普通for循环 ,其次foreach, +- 未实现RandomAccess接口的ist, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环 ### 补充:数据结构基础之双向链表 @@ -68,7 +80,7 @@ Arraylist不是同步的,所以在不需要保证线程安全时时建议使 ### JDK1.8之前 -JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** **所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** @@ -165,7 +177,7 @@ static int hash(int h) { ## HashMap 的长度为什么是2的幂次方 -为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483648,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 +为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 **这个算法应该如何设计呢?** @@ -190,6 +202,7 @@ static int hash(int h) { 这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让 A.next=B,由此,环形链表出现:B.next=A; A.next=B +**注意:jdk1.8已经解决了死循环的问题。**详细信息请阅读[jdk1.8 hashmap多线程put不会造成死循环](https://blog.csdn.net/qq_27007251/article/details/71403647) ## HashSet 和 HashMap 区别 @@ -203,7 +216,7 @@ static int hash(int h) { ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 - **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; -- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 +- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 **两者的对比图:** @@ -249,7 +262,8 @@ synchronized只锁定当前链表或红黑二叉树的首节点,这样只要ha #### 1. List - **Arraylist:** Object数组 - **Vector:** Object数组 - - **LinkedList:** 双向循环链表 + - **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) + 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html) #### 2. Set - **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素 diff --git a/README.md b/README.md index e5f58446a92..3affa224314 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,267 @@ -> 福利:看本文之前,推荐给大家一个阿里云双11活动,真的非常非常非常推荐,对于新人福利,阿里云这次真的是下血本了,建议阿里云新人一定一定一定不要错过。如果觉得这单纯是广告的话(阿里云肯找我做广告就好了,嘿嘿),你可以直接跳过看正文。 +# Java 学习/面试指南 + +**一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字“1”即可免费无套路获取。** + +**【限时福利】** 极客时间[《Java 并发编程面试必备》](#Java并发编程专栏)专栏限时特惠,购买之后的小伙伴加 [我的微信](#联系我) 报上自己的极客时间大名可以找我会把24元返现退给大家,减轻各位学习成本。 + + +为了优化大家的阅读体验,我重新进行了排版,并且增加了较为详细的[目录](#目录)供大家参考!对本仓库提过有价值的 issue 或 pr 的小伙伴将出现在 [Contributor](#Contributor) 这里。另外, 由于我个人能力有限,很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。**对于不错的原创文章我根据你的选择给予现金奖励、付费专栏或者书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。 + +
+ +
+ +微信交流群添加 [我的微信](#我的微信) 后回复关键字“加群”即可入群。 + +
+ +## 目录 + +* [:coffee: Java](#coffee-java) + * [Java/J2EE 基础](#javaj2ee-基础) + * [Java 集合框架](#java-集合框架) + * [Java 多线程](#java-多线程) + * [Java BIO,NIO,AIO](#java-bionioaio) + * [Java 虚拟机 jvm](#java-虚拟机-jvm) +* [:open_file_folder: 数据结构与算法](#open_file_folder-数据结构与算法) + * [数据结构](#数据结构) + * [算法](#算法) +* [:computer: 计算机网络与数据通信](#computer-计算机网络与数据通信) + * [网络相关](#网络相关) + * [数据通信\(RESTful,RPC,消息队列\)总结](#数据通信restfulrpc消息队列总结) +* [:iphone: 操作系统](#iphone-操作系统) + * [Linux相关](#linux相关) +* [:pencil2: 主流框架/软件](#pencil2-主流框架软件) + * [Spring](#spring) + * [ZooKeeper](#zookeeper) +* [:floppy_disk: 数据存储](#floppy_disk-数据存储) + * [MySQL](#mysql) + * [Redis](#redis) +* [:punch: 架构](#punch-架构) +* [:musical_note: 面试必备](#musical_note-面试必备) + * [备战面试](#备战面试) + * [最最最常见的Java面试题总结](#最最最常见的java面试题总结) + * [Java学习/面试开源仓库推荐](#java学习面试开源仓库推荐) +* [:art: 闲谈](#art-闲谈) +* [:envelope: 说明](#envelope-说明) + +## 待办 + +* [ ] Java 8 新特性总结 +* [x] BIO,NIO,AIO 总结 +* [ ] Netty 总结 +* [ ] 数据结构总结重构 + +## :coffee: Java + +### Java/J2EE 基础 -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有40多位新人了,现在是折上5折了也就是1折购买,已经达到了最低折扣!!!!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:[https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn](https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn) ! +* [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) +* [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) +* [Java常见关键字总结:static、final、this、super](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) -另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) + +### Java 集合框架 -| Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | -| :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| -| [Java](#coffee-Java) | [数据结构与算法](#open_file_folder-数据结构与算法)|[计算机网络与数据通信](#computer-计算机网络与数据通信) | [操作系统](#iphone-操作系统)| [主流框架](#pencil2-主流框架)| [数据存储](#floppy_disk-数据存储)|[架构](#punch-架构)| [面试必备](#musical_note-面试必备)| [其他](#art-其他)| [说明](#envelope-该开源文档一些说明)| +* [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) +* [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) +* [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) +* [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/ArrayList-Grow.md) +* [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) +* [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md) -## :coffee: Java -- ### Java/J2EE 基础 - - [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) - - [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) - - [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) - - [static 关键字详解](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/static.md) - -- ### Java 集合框架 - - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) - - [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) - - [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) - - [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/ArrayList-Grow.md) - - [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) - - [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md) - -- ### Java 多线程 - - [多线程系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/多线程系列.md) - - [Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) - -- ### Java IO 与 NIO - - [Java IO 与 NIO系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java%20IO与NIO.md) - -- ### Java虚拟机(jvm) - - [可能是把Java内存区域讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md) - - [搞定JVM垃圾回收就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/搞定JVM垃圾回收就是这么简单.md) - - [Java虚拟机(jvm)学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java虚拟机(jvm).md) -- ### 设计模式 - - [设计模式系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/设计模式.md) +### Java 多线程 + +* [多线程系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/多线程系列.md) +* [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) +* [并发编程面试必备:乐观锁与悲观锁](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/面试必备之乐观锁与悲观锁.md) +* [并发编程面试必备:JUC 中的 Atomic 原子类总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/Atomic.md) +* [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/AQS.md) +* [BATJ都爱问的多线程面试题](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/BATJ都爱问的多线程面试题.md) +* [并发容器总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/并发容器总结.md) + +### Java 虚拟机 jvm + +* [可能是把Java内存区域讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md) +* [搞定JVM垃圾回收就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/搞定JVM垃圾回收就是这么简单.md) +* [《深入理解Java虚拟机》第2版学习笔记](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java虚拟机(jvm).md) + +### Java BIO,NIO,AIO + +* [BIO,NIO,AIO 总结 ](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/BIO%2CNIO%2CAIO%20summary.md) +* [Java IO 与 NIO系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java%20IO与NIO.md) + +### 设计模式 + +* [设计模式系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/设计模式.md) ## :open_file_folder: 数据结构与算法 - -- ### 数据结构 - - [数据结构知识学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/数据结构.md) +### 数据结构 + +* [数据结构知识学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/数据结构.md) + +### 算法 -- ### 算法 - - [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md) - - - [常见安全算法(MD5、SHA1、Base64等等)总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) - - [算法总结——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) - - [算法总结——几道常见的链表算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/Leetcode-LinkList1.md) +* [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md) +* [常见安全算法(MD5、SHA1、Base64等等)总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) +* [算法总结——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) +* [算法总结——几道常见的链表算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/Leetcode-LinkList1.md) ## :computer: 计算机网络与数据通信 -- ### 网络相关 - - [计算机网络常见面试题](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/计算机网络.md) - - [计算机网络基础知识总结](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/干货:计算机网络知识总结.md) - -- ### 数据通信(RESTful、RPC、消息队列) - - [数据通信(RESTful、RPC、消息队列)相关知识点总结](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md) - + +### 网络相关 + +* [计算机网络常见面试题](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/计算机网络.md) +* [计算机网络基础知识总结](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/干货:计算机网络知识总结.md) +* [HTTPS中的TLS](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/HTTPS中的TLS.md) + +### 数据通信(RESTful,RPC,消息队列)总结 + +* [数据通信(RESTful、RPC、消息队列)相关知识点总结](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md) +* [Dubbo 总结:关于 Dubbo 的重要知识点](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/dubbo.md) +* [消息队列总结:新手也能看懂,消息队列其实很简单](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/message-queue.md) +* [一文搞懂 RabbitMQ 的重要概念以及安装](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/rabbitmq.md) ## :iphone: 操作系统 -- ### Linux相关 - - [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) +### Linux相关 +* [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) +* [Shell 编程入门](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/Shell.md) ## :pencil2: 主流框架/软件 -- ### Spring - - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - - [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md) - - [SpringMVC 工作原理详解](https://github.com/Snailclimb/JavaGuide/blob/master/主流框架/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md) -- ### ZooKeeper - - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) +### Spring + +* [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) +* [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md) +* [SpringMVC 工作原理详解](https://github.com/Snailclimb/JavaGuide/blob/master/主流框架/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md) + +### ZooKeeper + +* [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) +* [ZooKeeper 数据模型和常见命令了解一下,速度收藏!](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper数据模型和常见命令.md) ## :floppy_disk: 数据存储 -- ### MySQL - - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) - - [【思维导图-索引篇】搞定数据库索引就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL%20Index.md) -- ### Redis - - [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) - - [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) - - [如何做可靠的分布式锁,Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md)\ + +### MySQL + +* [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) +* [【思维导图-索引篇】搞定数据库索引就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL%20Index.md) + +### Redis + +* [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) +* [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) +* [如何做可靠的分布式锁,Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md) ## :punch: 架构 -- ### 分布式相关 - - [分布式学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) + +* [一文读懂分布式应该学什么](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) +* [8 张图读懂大型网站技术架构](https://github.com/Snailclimb/JavaGuide/blob/master/架构/8%20张图读懂大型网站技术架构.md) +* [【面试精选】关于大型网站系统架构你不得不懂的10个问题](https://github.com/Snailclimb/JavaGuide/blob/master/架构/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md) ## :musical_note: 面试必备 -- ### 面试必备知识点 - - [面试必备之乐观锁与悲观锁](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/面试必备之乐观锁与悲观锁.md) -- ### 最最最常见的Java面试题总结 - 这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 - - - [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) - - [第二周(2018-8-13)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) - - [第三周(2018-08-22)](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) - - [第四周(2018-8-30).md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) -- ### 程序员如何写简历 - - [程序员的简历之道](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/程序员的简历之道.md) - - [手把手教你用Markdown写一份高质量的简历](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/手把手教你用Markdown写一份高质量的简历.md) - -## :art: 其他 - -- ### 个人书单推荐 - - [个人阅读书籍清单](https://github.com/Snailclimb/Java-Guide/blob/master/其他/个人阅读书籍清单.md) - -- ### 技术方向选择 - - [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/其他/选择技术方向都要考虑哪些因素.md) - -- ### 2018 年秋招简单回顾 - - [结束了我短暂的秋招,说点自己的感受](https://github.com/Snailclimb/JavaGuide/blob/master/%E5%85%B6%E4%BB%96/2018%20%E7%A7%8B%E6%8B%9B.md) - +### 备战面试 + +* [【备战面试1】程序员的简历就该这样写](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/程序员的简历之道.md) +* [【备战面试2】初出茅庐的程序员该如何准备面试?](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/interviewPrepare.md) +* [【备战面试3】Java程序员必备书单](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/books.md) +* [【备战面试4】美团面试常见问题总结(附详解答案)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/美团面试常见问题总结.md) + + +### 最最最常见的Java面试题总结 + +这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 + +* [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) +* [第二周(2018-8-13)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) +* [第三周(2018-08-22)](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) +* [第四周(2018-8-30).md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) + +### Java学习/面试开源仓库推荐 + +* [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/JavaInterviewGithub.md) + +## :art: 闲谈 + +* [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/其他/选择技术方向都要考虑哪些因素.md) +* [结束了我短暂的秋招,说点自己的感受](https://github.com/Snailclimb/JavaGuide/blob/master/%E5%85%B6%E4%BB%96/2018%20%E7%A7%8B%E6%8B%9B.md) +* [这7个问题,可能大部分Java程序员都比较关心吧!](https://github.com/Snailclimb/JavaGuide/blob/master/%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87/java%20programmer%20need%20know.md) +* [【2018总结】即使平凡,也要热爱自己的生活](https://github.com/Snailclimb/JavaGuide/blob/master/%E5%85%B6%E4%BB%96/2018%20summary.md) *** -> # :envelope: 该开源文档一些说明 +## :envelope: 说明 + +### 介绍 + +* **对于 Java 初学者来说:** 本文档倾向于给你提供一个比较详细的学习路径,让你对于Java整体的知识体系有一个初步认识。另外,本文的一些文章 +也是你学习和复习 Java 知识不错的实践; +* **对于非 Java 初学者来说:** 本文档更适合回顾知识,准备面试,搞清面试应该把重心放在那些问题上。要搞清楚这个道理:提前知道那些面试常见,不是为了背下来应付面试,而是为了让你可以更有针对的学习重点。 + +本文档 Markdown 格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 + +### 关于转载 + +如果你需要转载本仓库的一些文章到自己的博客的话,记得注明原文地址就可以了。 + +### 如何对该开源文档进行贡献 -## 介绍 -该文档主要是笔主在学习Java的过程中的一些学习笔记,但是为了能够涉及到大部分后端学习所需的技术知识点我也会偶尔引用一些别人的优秀文章的链接。 -该文档涉及的主要内容包括: Java、 数据结构与算法、计算机网络与数据通信、 操作系统、主流框架、数据存储、架构、面试必备知识点等等。相信不论你是前端还是后端都能在这份文档中收获到东西。 -## 关于转载 +1. 笔记内容大多是手敲,所以难免会有笔误,你可以帮我找错别字。 +2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。(**对于不错的原创文章我根据你的选择给予现金奖励、付费专栏或者书籍进行奖励!快提 pr 给我投稿吧!**) +3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点的修改/补充。 -**如果需要引用到本仓库的一些东西,必须注明转载地址!!!毕竟大多都是手敲的,或者引用的是我的原创文章,希望大家尊重一下作者的劳动**:smiley::smiley::smiley:! +### 为什么要做这个开源文档? -## 如何对该开源文档进行贡献 +初始想法源于自己的个人那一段比较迷茫的学习经历。主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者面试过程中遇到问题的小伙伴。 -1. 笔记内容大多是手敲,所以难免会有笔误。 -2. 你对其他知识点的补充。 +### 联系我 -## 为什么要做这个开源文档? +如果大家需要与我交流,可以扫描下方二维码添加我的微信获取关注[我的公众号](#公众号): -在我们学习Java的时候,很多人会面临我不知道继续学什么或者面试会问什么的尴尬情况(我本人之前就很迷茫:smile:)。所以,我决定通过这个开源平台来帮助一些有需要的人,通过下面的内容,你会掌握系统的Java学习以及面试的相关知识。本来是想通过Gitbook的形式来制作的,后来想了想觉得可能有点大题小做 :grin: 。另外,我自己一个人的力量毕竟有限,希望各位有想法的朋友可以提issue。 +![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/JavaGuide.jpg) -## 最后 +### Java并发编程专栏 -本人会利用业余时间一直更新下去,目前还有很多地方不完善,一些知识点我会原创总结,还有一些知识点如果说网上有比较好的文章了,我会把这些文章加入进去。您也可以关注我的微信公众号:“Java面试通关手册”,我会在这里分享一些自己的原创文章。 另外该文档格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 +微信扫描下方二维码,购买之后我会将自己得到的24元返现都还给你,减轻各位的学习成本! -## 阿里云活动推荐 +![ Java并发编程专栏](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Java并发编程实战.jpg) -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! +### Contributor -> 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: +下面是笔主收集的一些对本仓库提过有价值的pr或者issue的朋友,人数较多,如果你也对本仓库提过不错的pr或者issue的话,你可以加我的微信与我联系。下面的排名不分先后! -1. [全民云计算:ECS云服务器2折起,1核1G仅需293元/年](https://promotion.aliyun.com/ntms/act/qwbk.html?userCode=hf47liqn) -2. [高性能企业级性能云服务器限时2折起,2核4G仅需720元/年](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn) -3. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.8849694.home.4.27a24b70kENhtV&userCode=hf47liqn) -4. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn) -5. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) + + + + + + + + + + + + + + + + +" + + + + + + + + + +### 公众号 -**你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号有大量资料,回复关键字“1”你可能看到想要的东西哦!:** +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。我是 ThoughtWorks 准入职Java工程师。专注Java知识分享!开源 Java 学习指南——JavaGuide(12k+ Star)的作者。公众号多篇文章被各大技术社区转载。公众号后台回复关键字“1”可以领取一份我精选的Java资源哦!可以扫描下方二维码或者通过微信的搜一搜搜索ID:“Java_Guide”即可。 -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-17/29079091.jpg) +![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" index 09e3b5d261b..4e8279e7d7d 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" @@ -23,6 +23,8 @@ **Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?** 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? **Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。** +在大多数情况下。单例 bean 是很理想的方案。不过,有时候你可能会发现你所使用的类是易变的,它们会保持一些状态,因此重用是不安全的。在这种情况下,将 class 声明为单例的就不是那么明智了。因为对象会被污染,稍后重用的时候会出现意想不到的问题。所以 Spring 定义了多种作用域的bean。 + # 一 bean的作用域 创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表。 @@ -65,7 +67,7 @@ public class ServiceImpl{ ### 3. request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效 -**request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。** 在 XML 中将 bean 定义成 prototype ,可以这样配置: +**request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。** 在 XML 中将 bean 定义成 request ,可以这样配置: ```java diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" index 732717aa3a6..dc56d44af82 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" @@ -116,7 +116,7 @@ Spring IOC的初始化过程: > ## Spring源码阅读 -阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己字面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 +阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己在面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 ### [Spring源码阅读](https://github.com/seaswalker/Spring) - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" index 0fc0abe3895..fa6d2b6568f 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" @@ -28,7 +28,7 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 **ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。** -**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。 +**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心(提供发布订阅服务)。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。 ![Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/35571782.jpg) @@ -58,7 +58,7 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 - **ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟**(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。 - **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。**(“读”多于“写”是协调服务的典型场景。) - **ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。** -- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务。 +- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提供数据节点监听服务。 **下面关于会话(Session)、 Znode、版本、Watcher、ACL概念的总结都在《从Paxos到Zookeeper 》第四章第一节以及第七章第八节有提到,感兴趣的可以看看!** @@ -74,11 +74,11 @@ Session 指的是 ZooKeeper 服务器与客户端会话。**在 ZooKeeper 中 Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。 -**在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。**另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。 +**在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。** 另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。 ### 2.4 版本 -在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 cversion(当前ZNode的ACL版本)。 +在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat 中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 aversion(当前ZNode的ACL版本)。 ### 2.5 Watcher diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" new file mode 100644 index 00000000000..401c752f049 --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" @@ -0,0 +1,202 @@ + + +- [ZooKeeper 数据模型](#zookeeper-数据模型) +- [ZNode\(数据节点\)的结构](#znode数据节点的结构) +- [测试 ZooKeeper 中的常见操作](#测试-zookeeper-中的常见操作) + - [连接 ZooKeeper 服务](#连接-zookeeper-服务) + - [查看常用命令\(help 命令\)](#查看常用命令help-命令) + - [创建节点\(create 命令\)](#创建节点create-命令) + - [更新节点数据内容\(set 命令\)](#更新节点数据内容set-命令) + - [获取节点的数据\(get 命令\)](#获取节点的数据get-命令) + - [查看某个目录下的子节点\(ls 命令\)](#查看某个目录下的子节点ls-命令) + - [查看节点状态\(stat 命令\)](#查看节点状态stat-命令) + - [查看节点信息和状态\(ls2 命令\)](#查看节点信息和状态ls2-命令) + - [删除节点\(delete 命令\)](#删除节点delete-命令) +- [参考](#参考) + + + +> 看本文之前如果你没有安装 ZooKeeper 的话,可以参考这篇文章:[《使用 SpringBoot+Dubbo 搭建一个简单分布式服务》](https://github.com/Snailclimb/springboot-integration-examples/blob/master/md/springboot-dubbo.md) 的 “开始实战 1 :zookeeper 环境安装搭建” 这部分进行安装(Centos7.4 环境下)。如果你想对 ZooKeeper 有一个整体了解的话,可以参考这篇文章:[《可能是把 ZooKeeper 概念讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/ZooKeeper.md) + +### ZooKeeper 数据模型 + +ZNode(数据节点)是 ZooKeeper 中数据的最小单元,每个ZNode上都可以保存数据,同时还是可以有子节点(这就像树结构一样,如下图所示)。可以看出,节点路径标识方式和Unix文件 +系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写人数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。 +![ZooKeeper 数据模型](https://images.gitbook.cn/95a192b0-1c56-11e9-9a8e-f3b01b1ea9aa) + +提到 ZooKeeper 数据模型,还有一个不得不得提的东西就是 **事务 ID** 。事务的ACID(Atomic:原子性;Consistency:一致性;Isolation:隔离性;Durability:持久性)四大特性我在这里就不多说了,相信大家也已经挺腻了。 + +在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求,**ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。 + + + +### ZNode(数据节点)的结构 + +每个 ZNode 由2部分组成: + +- stat:状态信息 +- data:数据内容 + +如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到) + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo +# 该数据节点关联的数据内容为空 +null +# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出 +cZxid = 0x2 +ctime = Tue Nov 27 11:05:34 CST 2018 +mZxid = 0x2 +mtime = Tue Nov 27 11:05:34 CST 2018 +pZxid = 0x3 +cversion = 1 +dataVersion = 0 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 0 +numChildren = 1 + +``` +这些状态信息其实就是 Stat 对象的格式化输出。Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务ID、版本信息和子节点个数等,如下图所示(图源:《从Paxos到Zookeeper 分布式一致性原理与实践》,下面会介绍通过 stat 命令查看数据节点的状态)。 + +**Stat 类:** + +![Stat 类](https://images.gitbook.cn/a841e740-1c55-11e9-b5b7-abf0ec0c666a) + +关于数据节点的状态信息说明(也就是对Stat 类中的各字段进行说明),可以参考下图(图源:《从Paxos到Zookeeper 分布式一致性原理与实践》)。 + +![数据节点的状态信息说明](https://images.gitbook.cn/f44d8630-1c55-11e9-b5b7-abf0ec0c666a) + +### 测试 ZooKeeper 中的常见操作 + + +#### 连接 ZooKeeper 服务 + +进入安装 ZooKeeper文件夹的 bin 目录下执行下面的命令连接 ZooKeeper 服务(Linux环境下)(连接之前首选要确定你的 ZooKeeper 服务已经启动成功)。 + +```shell +./zkCli.sh -server 127.0.0.1:2181 +``` +![连接 ZooKeeper 服务](https://images.gitbook.cn/153b84c0-1c59-11e9-9a8e-f3b01b1ea9aa) + +从上图可以看出控制台打印出了很多信息,包括我们的主机名称、JDK 版本、操作系统等等。如果你成功看到这些信息,说明你成功连接到 ZooKeeper 服务。 + +#### 查看常用命令(help 命令) + +help 命令查看 zookeeper 常用命令 + +![help 命令](https://images.gitbook.cn/091db640-1c59-11e9-b5b7-abf0ec0c666a) + +#### 创建节点(create 命令) + +通过 create 命令在根目录创建了node1节点,与它关联的字符串是"node1" + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1” +``` +通过 create 命令在根目录创建了node1节点,与它关联的内容是数字 123 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123 +Created /node1/node1.1 +``` + +#### 更新节点数据内容(set 命令) + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1" +``` + +#### 获取节点的数据(get 命令) + +get 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过set 命令已经将节点数据内容改为 "set node1"。 + +```shell +set node1 +cZxid = 0x47 +ctime = Sun Jan 20 10:22:59 CST 2019 +mZxid = 0x4b +mtime = Sun Jan 20 10:41:10 CST 2019 +pZxid = 0x4a +cversion = 1 +dataVersion = 1 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 9 +numChildren = 1 + +``` + +#### 查看某个目录下的子节点(ls 命令) + +通过 ls 命令查看根目录下的节点 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 37] ls / +[dubbo, zookeeper, node1] +``` +通过 ls 命令查看 node1 目录下的节点 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1 +[node1.1] +``` +zookeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径path下的所有子节点信息(列出1级,并不递归) + +#### 查看节点状态(stat 命令) + +通过 stat 命令查看节点状态 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1 +cZxid = 0x47 +ctime = Sun Jan 20 10:22:59 CST 2019 +mZxid = 0x47 +mtime = Sun Jan 20 10:22:59 CST 2019 +pZxid = 0x4a +cversion = 1 +dataVersion = 0 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 11 +numChildren = 1 +``` +上面显示的一些信息比如cversion、aclVersion、numChildren等等,我在上面 “ZNode(数据节点)的结构” 这部分已经介绍到。 + +#### 查看节点信息和状态(ls2 命令) + + +ls2 命令更像是 ls 命令和 stat 命令的结合。ls2 命令返回的信息包括2部分:子节点列表 + 当前节点的stat信息。 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1 +[node1.1] +cZxid = 0x47 +ctime = Sun Jan 20 10:22:59 CST 2019 +mZxid = 0x47 +mtime = Sun Jan 20 10:22:59 CST 2019 +pZxid = 0x4a +cversion = 1 +dataVersion = 0 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 11 +numChildren = 1 + +``` + +#### 删除节点(delete 命令) + +这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1 +``` + +在后面我会介绍到 Java 客户端 API的使用以及开源 Zookeeper 客户端 ZkClient 和 Curator 的使用。 + + +### 参考 + +- 《从Paxos到Zookeeper 分布式一致性原理与实践》 + diff --git "a/\345\205\266\344\273\226/2018 summary.md" "b/\345\205\266\344\273\226/2018 summary.md" new file mode 100644 index 00000000000..8d82e40b876 --- /dev/null +++ "b/\345\205\266\344\273\226/2018 summary.md" @@ -0,0 +1,174 @@ +# 【2018总结】即使平凡,也要热爱自己的生活 + +2018 年于我而讲,虽然平凡,但是自己就是在这平凡的一年也收货了很多东西。不光是自己学到的知识,我觉得 2018 年最大的幸运有三:其一是自己拥有了一份爱情,一份甜蜜的初恋,我真的很幸运遇到我现在的女朋友,愿以后的日子都能有她;其一是在 2018 年,我拥有了一份自己还算满意的 offer,马上就要毕业了,自己也要正式进去社会了;其一是自己在 2018 年的实现了自己的经济独立,这是一件让我很高兴的事情,我觉得大在学生时代实现经济独立还算是一件很不错的事情,花了这么多年父母的辛苦钱,自己也终于能替他们分担一点了。2018 年,感恩父母,感恩老师,感恩朋友,感恩遇到的每个善良的人,同时感恩2018年那个还算努力的自己。2019 继续加油! + +## 一份甜蜜的初恋(分手) + +先说说爱情。我和我的女朋友在一起已经半年多了,准确的来说截止到今天也就是 2018-12-30 号已经 190 天了。 + +![我和傻欢](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/54482393.jpg) + +我俩是异地,我在荆州,她在三亚。相见一面不管是时间上还是经济上对于还是学生的我们来说都甚是不易。见过很多人议论异地恋的种种不好,但是,于我而言,一份好的感情是值得被等待的。“待每一天如初恋,互相尊重彼此生活,共同努力,等老了就退隐山林养老......”,这应该是我和她之间最好的承诺了。 + +## 还算不错的学习收获 + +再来说说学习。这一年还算是让人满意,虽然我也不知道这一年自己到底学到了什么。如果你要问我这一年在学习上做的最满意的事情是什么,我还真不好回答,下面就从下面几个维度来简单谈一谈。 + +### 开源 + +这一年自己在Github上还是挺活跃的,提交了很多的代码和文档,同时也收获了很多的star、follower、pr、issue以及fork。 + +![我的Github概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/41250517.jpg) + +![我的Github贡献概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/33580466.jpg) + +开源的Java学习/面试指南—JavaGuide 某种程度上让我挺满意的,3月份开源 ,到现在的18k+ star 也算是可以用厚积薄发来形容了。但是,JavaGuide 也有很多让我不满意的,在2019年以及以后我也会继续完善。JavaGuide 地址:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) + +![JavaGuide 概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) + +### 技术博客 + +我更新的博客主要都是关于Java方面的,也更新了几篇Python的,有一篇Python的文章竟然在我的CSDN上面阅读霸榜。 +![霸榜的 Python 文章](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/19527688.jpg) + +在这一年,我更新了挺多技术文章,这里就不一一列举了,我贴一下自己觉得不错的文章吧! + +#### 最常见面试题系列 + +- [最最最常见的Java面试题总结——第一周](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484252&idx=1&sn=cb160d67fc1c0a95babc464b703df5e7&chksm=fd98553dcaefdc2b18f934957dd950aeaf04e90136099fa2817fffbd1e1df452b581e1caee17&token=1398134989&lang=zh_CN#rd) +- [最最最常见的Java面试题总结——第二周](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484282&idx=1&sn=7f986dc3263b6ca0f9e182145fdd40a1&chksm=fd98551bcaefdc0d5aff9577692881dc79765a339ce97e55958e23e1956aa7092dfac44b68f1&token=1398134989&lang=zh_CN#rd) +- [这几道Java集合框架面试题在面试中几乎必问](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484308&idx=1&sn=e3607919aed604be629617f867f46844&chksm=fd9855f5caefdce3f1ee72cb33b9b3bf9899fa2b64bbb92f1e820c0ef3985245b1f7dfc05358&token=1398134989&lang=zh_CN#rd) +- [如果不会这几道多线程基础题,请自觉面壁!](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484337&idx=1&sn=d5e953d4b2da7ed37a7f843bfb437ed8&chksm=fd9855d0caefdcc65cb2e5cc0c69d27f785fc41477bcf55fff2cdff3268b0b078eb1a5107726&token=1398134989&lang=zh_CN#rd) +- [值得立马保存的 synchronized 关键字总结](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484355&idx=1&sn=6da29974b6dd1a4aa0d032f44d5fa8de&chksm=fd9855a2caefdcb4c370814baafd4baca27dfccaf609c9edf82370637ba4856176ab143a375e&token=1398134989&lang=zh_CN#rd) +- [【面试必备】源码角度一步一步分析 ArrayList 扩容机制](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=1&sn=1b6155015fedfc9f78fabecc18da7b18&chksm=fd985591caefdc870cb018d27f92e1908b6c6e22816a77ead03c4e44b2f53caec00871172b1f&token=1398134989&lang=zh_CN#rd) +- [Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1398134989&lang=zh_CN#rd) + +#### Github + +- [近几个月Github上最热门的Java项目一览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484188&idx=1&sn=40037de4844f62316465bbe4e910c69c&chksm=fd98557dcaefdc6bedcaeb275aae7c340d46cf6ab0dc96e49c51982f9c53d6a44de283efc9a8&token=1398134989&lang=zh_CN#rd) +- [推荐10个Java方向最热门的开源项目(8月)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484333&idx=1&sn=8c97b029692877a537d55175a8c82977&chksm=fd9855cccaefdcdaffe0558ba5e8dca415495935b0ad1181e6b148b08e1c86ce5d841e9df901&token=1398134989&lang=zh_CN#rd) +- [Github上 Star 数相加超过 7w+ 的三个面试相关的仓库推荐](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484644&idx=1&sn=5016caaf97e498b76de2189e3f55e9dc&chksm=fd985285caefdb93f4e3c7545d30edac6ad31b99f1fcc4503350101f0b20bba9a9705ed7d124&token=1398134989&lang=zh_CN#rd) +- [11月 Github Trending 榜最热门的 10 个 Java 项目](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484730&idx=1&sn=86e35dfea1478221b6d14a263e88ac89&chksm=fd98535bcaefda4d4f03bf0cd2e0a8fd9f44b1a2b118457a0c8b3de2ff8a1f4c4b7cd083f40e&token=1398134989&lang=zh_CN#rd) +- [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=1398134989&lang=zh_CN#rd) + + +#### 备战面试系列 + +- [可能是一份最适合你的后端面试指南(部分内容前端同样适用)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484529&idx=1&sn=9c7a3d6ad124affcadc19b0ff49bf68a&chksm=fd985210caefdb0615a9643fa698cb6267e89562730423841d942cde17ec9c1280dfc3a2b933&token=1398134989&lang=zh_CN#rd) +- [【备战春招/秋招系列1】程序员的简历就该这样写](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484573&idx=1&sn=8c5965d4a3710d405d8e8cc10c7b0ce5&chksm=fd9852fccaefdbea8dfe0bc40188b7579f1cddb1e8905dc981669a3f21d2a04cadceafa9023f&token=1990180468&lang=zh_CN#rd) +- [【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484578&idx=1&sn=eea72d80a2325257f00aaed21d5b226f&chksm=fd9852c3caefdbd52dd8a537cc723ed1509314401b3a669a253ef5bc0360b6fddef48b9c2e94&token=1990180468&lang=zh_CN#rd) +- [【备战春招/秋招系列3】Java程序员必备书单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484592&idx=1&sn=6d9731ce7401be49e97c1af6ed384ecc&chksm=fd9852d1caefdbc720a361ae65a8ad9d53cfb4800b15a7c68cbdc630b313215c6c52e0934ec2&token=1990180468&lang=zh_CN#rd) +- [【备战春招/秋招系列】美团面经总结基础篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484601&idx=1&sn=4907b7fef0856791c565d49d788ba8cc&chksm=fd9852d8caefdbce88e51c0a10a4ec77c97f382fd2af4a840ea47cffc828bfd0f993f50d5f0d&token=1895808268&lang=zh_CN#rd) +- [【备战春招/秋招系列】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=1895808268&lang=zh_CN#rd) +- [【备战春招/秋招系列】美团Java面经总结终结篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484668&idx=1&sn=9d4631588393075d9c453f307410f0cd&chksm=fd98529dcaefdb8b5497d1f161834af6917c33ea3d305eb41872e522707fa94218769ca60101&token=1398134989&lang=zh_CN#rd) +- [GitHub 上四万 Star 大佬的求职回忆](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484739&idx=1&sn=25cf5b36090f69299150663bdccfeec2&chksm=fd985322caefda34df0734efa607114704d1937f083aee2230b797d1f5aa04f7d13bf2f81dc5&token=1398134989&lang=zh_CN#rd)(非原创) + + +#### 并发编程面试必备 + +- [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&scene=21#wechat_redirect) +- [并发编程面试必备:JUC 中的 Atomic 原子类总结](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484553&idx=1&sn=aca9fa19f723206eff7e33a10973a887&chksm=fd9852e8caefdbfe7180c34f83bbb422a1a0bef1ed44b1e84f56924244ea3fd2da720f25c6dd#rd) +- [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484559&idx=1&sn=28dae85c38c4c500201c39234d25d731&chksm=fd9852eecaefdbf80cc54a25204e7c7d81170ce659acf92b7fa4151799ca3d0d7df2225d4ff1#rd) +- [并发编程面试必备:BATJ都爱问的多线程面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484564&idx=1&sn=d8467fdc5c1b3883e9b99485f7b0fb9a&chksm=fd9852f5caefdbe364d1c438865cff84acd8f40c1c9e2f9f5c8fef673b30f905b4c5f5255368&token=1398134989&lang=zh_CN#rd) + +#### 虚拟机 + +- [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=1398134989&lang=zh_CN#rd) +- [搞定 JVM 垃圾回收就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484328&idx=1&sn=214f5e18a6afa096eb552fd8627e0cea&chksm=fd9855c9caefdcdf70c746c74d31f65bbb109eedaea0cfe311a1e10af666047df59ff04c873b&token=1398134989&lang=zh_CN#rd) + +#### Spring Boot + +- [超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1398134989&lang=zh_CN#rd) +- [基于 SpringBoot2.0+优雅整合 SpringBoot+Mybatis](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484730&idx=2&sn=9be4636dd9a416b46f9029df68fad232&chksm=fd98535bcaefda4dccf14a286a24fcd2b3d4ab0d0e4d89dfbc955df99d2b06a1e17392b3c10b&token=1398134989&lang=zh_CN#rd) +- [新手也能实现,基于SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484737&idx=1&sn=e39693d845f022d689437ee58948ef6a&chksm=fd985320caefda36d5ab8abd52f5516c11cc5d1104608695bcea5909602b28dc40c132d6d46c&token=1398134989&lang=zh_CN#rd) +- [SpringBoot 整合 阿里云OSS 存储服务,快来免费搭建一个自己的图床](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484745&idx=1&sn=dbeec694916d204605929244d48a6b1c&chksm=fd985328caefda3e793170d81433c7c0b7dc1c4a4ae99395cce23b1d1a239482fd5bf1d89bc6&token=1398134989&lang=zh_CN#rd) + +#### 成长 + +- [结束了我短暂的秋招,说点自己的感受](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484516&idx=1&sn=4e2320613e76dd73a130c63beebbc3ca&chksm=fd985205caefdb13b4b611ed3c604d95314d28d567ec0c3b44585b89a7dc3142bcd52bc2d4cb&token=1398134989&lang=zh_CN#rd) +- [保研之路:从双非到南大](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484477&idx=1&sn=3b597e2431611aacca2b5d671a309d85&chksm=fd98525ccaefdb4a7e3742b5958244d453efe26f61f42f9f108190a0c18313f083189f10944e&token=1398134989&lang=zh_CN#rd)(非原创) +- [【周日闲谈】最近想说的几件小事](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484650&idx=1&sn=e97ea1eeebdb5def58bae1949bec9448&chksm=fd98528bcaefdb9d76ac62fd10544f058b1fee4a40ffd06ab9312b7eb3a62f86d67ea653b88a&token=1398134989&lang=zh_CN#rd) +- [这7个问题,可能大部分Java程序员都比较关心吧!](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484836&idx=1&sn=a6ada99c9506af01dc3bb472f66c57be&chksm=fd9853c5caefdad3034dbed00cf04412ea990fc05b6168720e6828ae6c90c9a885793acd7a14&token=1398134989&lang=zh_CN#rd) + +#### Docker + +- [可能是把Docker的概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484127&idx=1&sn=70ee95619ec761da884c4f9af3e83194&chksm=fd9854becaefdda81a02bf6cf9bd07a2fc879efa7cefc79691a0d319b501d8572e8bad981d87&token=1398134989&lang=zh_CN#rd) + +#### Linux + +- [后端程序员必备的Linux基础知识](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484157&idx=1&sn=8b47e623e83fb3666bce7c680e4649b8&chksm=fd98549ccaefdd8ad815f3d8eaca86cc7e7245b4f8de1d23897af3017f5fdb3f152734c40f5e&token=1398134989&lang=zh_CN#rd) +- [快速入门大厂后端面试必备的 Shell 编程](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484696&idx=1&sn=d3c1ba5abc10c10ff844cae2109a2628&chksm=fd985379caefda6faff8e050b7dfa1e92fbfe2912e44150cb4ae349aea807836166355062970&token=1398134989&lang=zh_CN#rd) + +#### ZooKeeper + +- [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484379&idx=1&sn=036f5d3defa8a6979afb77acc82a9517&chksm=fd9855bacaefdcacc1462f781b634e5599f2ee9e806bd24297dae4af0e4196a70ca6bbd8c354&token=1398134989&lang=zh_CN#rd) + +#### Redis + +- [redis 总结——重构版](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484450&idx=1&sn=7ee03fa67aecd05c5becd2a8259d3631&chksm=fd985243caefdb554ebab9149e750ac0c819074c57bd208f2d7f097fbc461ed58223e71c05f1&token=1398134989&lang=zh_CN#rd) +- [史上最全Redis高可用技术解决方案大全](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484478&idx=1&sn=a1250d9b8025cd7cb6fc6a58238ab51e&chksm=fd98525fcaefdb499a027df0138c98d4b02d828f27bd6144a4d40a1c088d340c29dd53d4a026&token=1398134989&lang=zh_CN#rd)(非原创) + +#### 计算机网络 + +- [搞定计算机网络面试,看这篇就够了(补充版)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484289&idx=1&sn=6b556843c60aac9a17b0e7c2e3cd6bca&chksm=fd9855e0caefdcf6af4123b719448c81d90c5442d4052ae01a4698047e226c0c18c14b2cc54a&token=1398134989&lang=zh_CN#rd) + +#### 数据库 + +- [【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1398134989&lang=zh_CN#rd) + +#### 消息队列 + +- [新手也能看懂,消息队列其实很简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484789&idx=1&sn=ba972f0aac39e9a28b29ddf92fc15c18&chksm=fd985314caefda0278235427d43846b6374ff32f4149352dec063287cbf9733b888acbb79923&token=1398134989&lang=zh_CN#rd) +- [一文搞懂 RabbitMQ 的重要概念以及安装](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484796&idx=1&sn=bc56fecb634732669cfe7db8d1820ded&chksm=fd98531dcaefda0b07b8a9c13429ef225d36a6e287c96c53d7aa3dfd65c62ccd60d13b22ebbf&token=1398134989&lang=zh_CN#rd) + +### 读书 + +推荐一下 2018 年看过的书籍,小部分可能2017年也看过一些。 + +#### 已看完 + +- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 +- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 +- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! +- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 +- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 +- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 +- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! +- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 +- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 +- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) +- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 + + +#### 未看完 + + +- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 +- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 +- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 +- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 +- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 +- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 +- [《RabbitMQ实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一 +- [《Spring Cloud微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 +- [《第一本Docker书》](https://book.douban.com/subject/26780404/):Docker入门书籍! +- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 +- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 + +## 一些个人愚见 + +### 关于读书 + +不知道大家收藏栏是不是和我一样收藏了很多文章,但是有多少篇是你真真认真看的呢?或者那几篇你认真看之后,经过一个月之后还记得这篇文章的大概内容。现在这个社会真是一个信息爆炸的社会,我个人真的深有感触,就在刚刚我还取消关注了好多微信公众号,很多公众号给我推的文章都有好几十篇了,但是我一篇都没有看,所以索性取消关注,省个位置。我个人觉得遇到好的文章,我们不光要读,而且要记录下来。就拿我本人来说,我平时喜欢用 OneNote 来记录学习笔记以及其他我感觉重要的事情比如重要人的生日啦这些。每当遇到自己喜欢的文章的时候,我都先会把文章的地址保存到我分好类的笔记本上,我会先进行第一遍阅读,第一遍我会读的很仔细,如果晦涩难懂的话我会先快速把总体看一遍,然后在细细品读。一般第二遍的时候我就会在笔记本上记录这篇文章的一些要点,以便我日后看到这些要点可以快速回忆起整篇文章的内容。如果某篇文章的知识点太过庞大的话,我会去选择采用思维导图的方式展示要点。看视频一样,看教学视频的话,如果觉得老师讲的不错,我们不妨记录下来,Onenote 或者有道云笔记都行,记录大概,够我们日后回忆就好。 + +### 关于学习 + +做事不要有功利性,我最早在掘金写文章,其实也只是为了记录自己的学习,没想到会有人喜欢自己的文章,另外我课外学的很多东西,我自己也不清楚以后工作会不会用到,反正我自己感觉即然自己感兴趣,那么就去学吧。我相信,很多东西可能暂时带给你不了带多实质性的帮助,但是总有一天它会对你有帮助。如果感到迷茫的话,就做好眼前的事(拿我们班主任的话说,如果你感到迷茫,你就学好现在的专业知识就好了),我觉得没毛病。 + +### 关于个人 + +在生活中一定要保持谦虚,保持谦虚,保持谦虚,时刻都要有反省的准备,你要记住学无止境,永远不要满足现在的现状。另外,就是一定要掌控好自己的时间,多留点时间给父母亲人,以及那些自己在乎的人。如果对别人很在乎的话,不要去装作不在乎,因为这样真的不是太好,虽然我之前也会这样,很多时候撰写的消息,最后没发出去。 + +## 最后分享一句话 + +分享给大家,我笔记本里一直保存的杨绛老先生的一句话:“我们曾如此渴望命运的波澜,到最后才发现:人生最曼妙的风景,竟是内心的淡定与从容……我们曾如此期盼外界的认可,到最后才知道:世界是自己的,与他人毫无关系!”。 diff --git "a/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" "b/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" index fb53c0ff164..f14ee33a211 100644 --- "a/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" +++ "b/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" @@ -75,7 +75,10 @@ - :thumbsup: [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/) 豆瓣 9.1 分,重构书籍的开山鼻祖。 +> ### linux操作系统相关 +- :thumbsup:[<>](https://book.douban.com/subject/25900403/) :thumbsup: [<>](https://book.douban.com/subject/1500149/) + 对于理解linux操作系统原理非常有用,同时可以打好个人的基本功力,面试中很多公司也会问到linux知识。 > ### 课外书籍 《技术奇点》 :thumbsup:《追风筝的人》 :thumbsup:《穆斯林的葬礼》 :thumbsup:《三体》 《人工智能——李开复》 diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" new file mode 100644 index 00000000000..4179f2702bb --- /dev/null +++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" @@ -0,0 +1,557 @@ + + + +- [Shell 编程入门](#shell-编程入门) + - [走进 Shell 编程的大门](#走进-shell-编程的大门) + - [为什么要学Shell?](#为什么要学shell) + - [什么是 Shell?](#什么是-shell) + - [Shell 编程的 Hello World](#shell-编程的-hello-world) + - [Shell 变量](#shell-变量) + - [Shell 编程中的变量介绍](#shell-编程中的变量介绍) + - [Shell 字符串入门](#shell-字符串入门) + - [Shell 字符串常见操作](#shell-字符串常见操作) + - [Shell 数组](#shell-数组) + - [Shell 基本运算符](#shell-基本运算符) + - [算数运算符](#算数运算符) + - [关系运算符](#关系运算符) + - [逻辑运算符](#逻辑运算符) + - [布尔运算符](#布尔运算符) + - [字符串运算符](#字符串运算符) + - [文件相关运算符](#文件相关运算符) + - [shell流程控制](#shell流程控制) + - [if 条件语句](#if-条件语句) + - [for 循环语句](#for-循环语句) + - [while 语句](#while-语句) + - [shell 函数](#shell-函数) + - [不带参数没有返回值的函数](#不带参数没有返回值的函数) + - [有返回值的函数](#有返回值的函数) + - [带参数的函数](#带参数的函数) + + + +# Shell 编程入门 + +## 走进 Shell 编程的大门 + +### 为什么要学Shell? + +学一个东西,我们大部分情况都是往实用性方向着想。从工作角度来讲,学习 Shell 是为了提高我们自己工作效率,提高产出,让我们在更少的时间完成更多的事情。 + +很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做Linux运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是shell编程也是我们必须要掌握的! + +目前Linux系统下最流行的运维自动化语言就是Shell和Python了。 + +两者之间,Shell几乎是IT企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,shell是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过web访问等。Shell是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。 + +另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。 + +![大型互联网公司对于shell编程技能的要求](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/60190220.jpg) + +### 什么是 Shell? + +简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。 + + +W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。 +![什么是 Shell?](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-26/19456505.jpg) + + +### Shell 编程的 Hello World + +学习任何一门编程语言第一件事就是输出HelloWord了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。 + + +(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了) + +(2) 使脚本具有执行权限:`chmod +x helloworld.sh` + +(3) 使用 vim 命令修改helloworld.sh文件:`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。)) + +helloworld.sh 内容如下: + +```shell +#!/bin/bash +#第一个shell小程序,echo 是linux中的输出命令。 +echo "helloworld!" +``` + +shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中,除了bash shell以外,还有很多版本的shell, 例如zsh、dash等等...不过bash shell还是我们使用最多的。** + + +(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。) + +![shell 编程Hello World](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/55296212.jpg) + + +## Shell 变量 + +### Shell 编程中的变量介绍 + + +**Shell编程中一般分为三种变量:** + +1. **我们自己定义的变量(自定义变量):** 仅在当前 Shell 实例中有效,其他 Shell 启动的程序不能访问局部变量。 +2. **Linux已定义的环境变量**(环境变量, 例如:$PATH, $HOME 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。 +3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行 + +**常用的环境变量:** +> PATH 决定了shell将到哪些目录中寻找命令或程序 +HOME 当前用户主目录 +HISTSIZE 历史记录数 +LOGNAME 当前用户的登录名 +HOSTNAME 指主机的名称 +SHELL 当前用户Shell类型 +LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 +MAIL 当前用户的邮件存放目录 +PS1 基本提示符,对于root用户是#,对于普通用户是$ + +**使用 Linux 已定义的环境变量:** + +比如我们要看当前用户目录可以使用:`echo $HOME`命令;如果我们要看当前用户Shell类型 可以使用`echo $SHELL`命令。可以看出,使用方法非常简单。 + +**使用自己定义的变量:** + +```shell +#!/bin/bash +#自定义变量hello +hello="hello world" +echo $hello +echo "helloworld!" +``` +![使用自己定义的变量](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/19835037.jpg) + + +**Shell 编程中的变量名的命名的注意事项:** + + +- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,但是可以使用下划线(_)开头。 +- 中间不能有空格,可以使用下划线(_)。 +- 不能使用标点符号。 +- 不能使用bash里的关键字(可用help命令查看保留关键字)。 + + +### Shell 字符串入门 + +字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和Java中有所不同。 + +**单引号字符串:** + +```shell +#!/bin/bash +name='SnailClimb' +hello='Hello, I am '$name'!' +echo $hello +``` +输出内容: + +``` +Hello, I am SnailClimb! +``` + +**双引号字符串:** + +```shell +#!/bin/bash +name='SnailClimb' +hello="Hello, I am "$name"!" +echo $hello +``` + +输出内容: + +``` +Hello, I am SnailClimb! +``` + + +### Shell 字符串常见操作 + +**拼接字符串:** + +```shell +#!/bin/bash +name="SnailClimb" +# 使用双引号拼接 +greeting="hello, "$name" !" +greeting_1="hello, ${name} !" +echo $greeting $greeting_1 +# 使用单引号拼接 +greeting_2='hello, '$name' !' +greeting_3='hello, ${name} !' +echo $greeting_2 $greeting_3 +``` + +输出结果: + +![输出结果](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/51148933.jpg) + + +**获取字符串长度:** + +```shell +#!/bin/bash +#获取字符串长度 +name="SnailClimb" +# 第一种方式 +echo ${#name} #输出 10 +# 第二种方式 +expr length "$name"; +``` + +输出结果: +``` +10 +10 +``` + +使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身: + +```shell +expr 5+6 // 直接输出 5+6 +expr 5 + 6 // 输出 11 +``` +对于某些运算符,还需要我们使用符号"\"进行转义,否则就会提示语法错误。 + +```shell +expr 5 * 6 // 输出错误 +expr 5 \* 6 // 输出30 +``` + +**截取子字符串:** + +简单的字符串截取: + + +```shell +#从字符串第 1 个字符开始往后截取 10 个字符 +str="SnailClimb is a great man" +echo ${str:0:10} #输出:SnailClimb +``` + +根据表达式截取: + +```shell +#!bin/bash +#author:amau + +var="http://www.runoob.com/linux/linux-shell-variable.html" + +s1=${var%%t*}#h +s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h +s3=${var%%.*}#http://www +s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html +s5=${var##*/}#linux-shell-variable.html +``` + +### Shell 数组 + +bash支持一维数组(不支持多维数组),并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。 + + +```shell +#!/bin/bash +array=(1 2 3 4 5); +# 获取数组长度 +length=${#array[@]} +# 或者 +length2=${#array[*]} +#输出数组长度 +echo $length #输出:5 +echo $length2 #输出:5 +# 输出数组第三个元素 +echo ${array[2]} #输出:3 +unset array[1]# 删除下表为1的元素也就是删除第二个元素 +for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5 +unset arr_number; # 删除数组中的所有元素 +for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容 +``` + + +## Shell 基本运算符 + +> 说明:图片来自《菜鸟教程》 + + Shell 编程支持下面几种运算符 + +- 算数运算符 +- 关系运算符 +- 布尔运算符 +- 字符串运算符 +- 文件测试运算符 + +### 算数运算符 + +![算数运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/4937342.jpg) + +我以加法运算符做一个简单的示例: + +```shell +#!/bin/bash +a=3;b=3; +val=`expr $a + $b` +#输出:Total value : 6 +echo "Total value : $val +``` + + +### 关系运算符 + +关系运算符只支持数字,不支持字符串,除非字符串的值是数字。 + +![shell关系运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/64391380.jpg) + +通过一个简单的示例演示关系运算符的使用,下面shell程序的作用是当score=100的时候输出A否则输出B。 + +```shell +#!/bin/bash +score=90; +maxscore=100; +if [ $score -eq $maxscore ] +then + echo "A" +else + echo "B" +fi +``` + +输出结果: + +``` +B +``` + +### 逻辑运算符 + +![逻辑运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60545848.jpg) + +示例: + +```shell +#!/bin/bash +a=$(( 1 && 0)) +# 输出:0;逻辑与运算只有相与的两边都是1,与的结果才是1;否则与的结果是0 +echo $a; +``` + +### 布尔运算符 + + +![布尔运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/93961425.jpg) + +这里就不做演示了,应该挺简单的。 + +### 字符串运算符 + +![ 字符串运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/309094.jpg) + +简单示例: + +```shell + +#!/bin/bash +a="abc"; +b="efg"; +if [ $a = $b ] +then + echo "a 等于 b" +else + echo "a 不等于 b" +fi +``` +输出: + +``` +a 不等于 b +``` + +### 文件相关运算符 + +![文件相关运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60359774.jpg) + +使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。 + +## shell流程控制 + +### if 条件语句 + +简单的 if else-if else 的条件语句示例 + +```shell +#!/bin/bash +a=3; +b=9; +if [ $a = $b ] +then + echo "a 等于 b" +elif [ $a > $b ] +then + echo "a 大于 b" +else + echo "a 小于 b" +fi +``` + +输出结果: + +``` +a 大于 b +``` + +相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。 + +### for 循环语句 + +通过下面三个简单的示例认识 for 循环语句最基本的使用,实际上 for 循环语句的功能比下面你看到的示例展现的要大得多。 + +**输出当前列表中的数据:** + +```shell +for loop in 1 2 3 4 5 +do + echo "The value is: $loop" +done +``` + +**产生 10 个随机数:** + +```shell +#!/bin/bash +for i in {0..9}; +do + echo $RANDOM; +done +``` + +**输出1到5:** + +通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要,下面来看一个例子: + +```shell +#!/bin/bash +for((i=1;i<=5;i++));do + echo $i; +done; +``` + + +### while 语句 + +**基本的 while 循环语句:** + +```shell +#!/bin/bash +int=1 +while(( $int<=5 )) +do + echo $int + let "int++" +done +``` + +**while循环可用于读取键盘信息:** + +```shell +echo '按下 退出' +echo -n '输入你最喜欢的电影: ' +while read FILM +do + echo "是的!$FILM 是一个好电影" +done +``` + +输出内容: + +``` +按下 退出 +输入你最喜欢的电影: 变形金刚 +是的!变形金刚 是一个好电影 +``` + +**无线循环:** + +```shell +while true +do + command +done +``` + +## shell 函数 + +### 不带参数没有返回值的函数 + +```shell +#!/bin/bash +function(){ + echo "这是我的第一个 shell 函数!" +} +function +``` + +输出结果: + +``` +这是我的第一个 shell 函数! +``` + + +### 有返回值的函数 + +**输入两个数字之后相加并返回结果:** + +```shell +#!/bin/bash +funWithReturn(){ + echo "输入第一个数字: " + read aNum + echo "输入第二个数字: " + read anotherNum + echo "两个数字分别为 $aNum 和 $anotherNum !" + return $(($aNum+$anotherNum)) +} +funWithReturn +echo "输入的两个数字之和为 $?" +``` + +输出结果: + +``` +输入第一个数字: +1 +输入第二个数字: +2 +两个数字分别为 1 和 2 ! +输入的两个数字之和为 3 +``` + +### 带参数的函数 + + + +```shell +#!/bin/bash +funWithParam(){ + echo "第一个参数为 $1 !" + echo "第二个参数为 $2 !" + echo "第十个参数为 $10 !" + echo "第十个参数为 ${10} !" + echo "第十一个参数为 ${11} !" + echo "参数总数有 $# 个!" + echo "作为一个字符串输出所有参数 $* !" +} +funWithParam 1 2 3 4 5 6 7 8 9 34 73 + +``` + +输出结果: + +``` +第一个参数为 1 ! +第二个参数为 2 ! +第十个参数为 10 ! +第十个参数为 34 ! +第十一个参数为 73 ! +参数总数有 11 个! +作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 ! + +``` diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" index 35edec3742e..65cc9eaec0e 100644 --- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,8 +1,31 @@ + + +- [一 从认识操作系统开始](#一-从认识操作系统开始) + - [1.1 操作系统简介](#11-操作系统简介) + - [1.2 操作系统简单分类](#12-操作系统简单分类) +- [二 初探Linux](#二-初探linux) + - [2.1 Linux简介](#21-linux简介) + - [2.2 Linux诞生简介](#22-linux诞生简介) + - [2.3 Linux的分类](#23-linux的分类) +- [三 Linux文件系统概览](#三-linux文件系统概览) + - [3.1 Linux文件系统简介](#31-linux文件系统简介) + - [3.2 文件类型与目录结构](#32-文件类型与目录结构) +- [四 Linux基本命令](#四-linux基本命令) + - [4.1 目录切换命令](#41-目录切换命令) + - [4.2 目录的操作命令(增删改查)](#42-目录的操作命令增删改查) + - [4.3 文件的操作命令(增删改查)](#43-文件的操作命令增删改查) + - [4.4 压缩文件的操作命令](#44-压缩文件的操作命令) + - [4.5 Linux的权限命令](#45-linux的权限命令) + - [4.6 Linux 用户管理](#46-linux-用户管理) + - [4.7 Linux系统用户组的管理](#47-linux系统用户组的管理) + - [4.8 其他常用命令](#48-其他常用命令) + + > 学习Linux之前,我们先来简单的认识一下操作系统。 ## 一 从认识操作系统开始 -### 1.1 操作系统简介 +### 1.1 操作系统简介 我通过以下四点介绍什么操作系统: @@ -12,7 +35,7 @@ - **操作系统分内核与外壳(我们可以把外壳理解成围绕着内核的应用程序,而内核就是能操作硬件的程序)。** ![操作系统分内核与外壳](https://user-gold-cdn.xitu.io/2018/7/3/1645ee3dc5cf626e?w=862&h=637&f=png&s=23899) -### 1.2 操作系统简单分类 +### 1.2 操作系统简单分类 1. **Windows:** 目前最流行的个人桌面操作系统 ,不做多的介绍,大家都清楚。 2. **Unix:** 最早的多用户、多任务操作系统 .按照操作系统的分类,属于分时操作系统。Unix 大多被用在服务器、工作站,现在也有用在个人计算机上。它在创建互联网、计算机网络或客户端/服务器模型方面发挥着非常重要的作用。 @@ -22,7 +45,7 @@ ![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645eeb8e843f29d?w=426&h=240&f=png&s=32650) -## 二 初探Linux +## 二 初探Linux ### 2.1 Linux简介 @@ -33,7 +56,7 @@ ![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645ef0a5a4f137f?w=270&h=376&f=png&s=193487) -### 2.2 Linux诞生简介 +### 2.2 Linux诞生简介 - 1991年,芬兰的业余计算机爱好者Linus Torvalds编写了一款类似Minix的系统(基于微内核架构的类Unix操作系统)被ftp管理员命名为Linux 加入到自由软件基金的GNU计划中; - Linux以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。 @@ -48,7 +71,7 @@ ![Linux发行版本](https://user-gold-cdn.xitu.io/2018/7/3/1645efa7048fd018?w=548&h=274&f=png&s=99213) -## 三 Linux文件系统概览 +## 三 Linux文件系统概览 ### 3.1 Linux文件系统简介 @@ -75,34 +98,34 @@ Linux文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是 - **/usr :** 用于存放系统应用程序; - **/opt:** 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里; - **/proc:** 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息; -- **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^); -- **/sbin:** 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等; -- **/dev:** 用于存放设备文件; -- **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统; -- **/boot:** 存放用于系统引导时使用的各种文件; +- **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^); +- **/sbin:** 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等; +- **/dev:** 用于存放设备文件; +- **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统; +- **/boot:** 存放用于系统引导时使用的各种文件; - **/lib :** 存放着和系统运行相关的库文件 ; -- **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点; -- **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等; -- **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里。 +- **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点; +- **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等; +- **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里。 -## 四 Linux基本命令 +## 四 Linux基本命令 下面只是给出了一些比较常用的命令。推荐一个Linux命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。 Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/) ### 4.1 目录切换命令 -- **`cd usr`:** 切换到该目录下usr目录 -- **`cd ..(或cd../)`:** 切换到上一层目录 -- **`cd /`:** 切换到系统根目录 -- **`cd ~`:** 切换到用户主目录 -- **`cd -`:** 切换到上一个所在目录 +- **`cd usr`:** 切换到该目录下usr目录 +- **`cd ..(或cd../)`:** 切换到上一层目录 +- **`cd /`:** 切换到系统根目录 +- **`cd ~`:** 切换到用户主目录 +- **`cd -`:** 切换到上一个操作所在目录 -### 4.2 目录的操作命令(增删改查) +### 4.2 目录的操作命令(增删改查) 1. **`mkdir 目录名称`:** 增加目录 -2. **`ls或者ll`**(ll是ls -l的缩写,ll命令以看到该目录下的所有目录和文件的详细信息):查看目录信息 +2. **`ls或者ll`**(ll是ls -l的别名,ll命令可以看到该目录下的所有目录和文件的详细信息):查看目录信息 3. **`find 目录 参数`:** 寻找目录(查) 示例: @@ -114,35 +137,35 @@ Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/) 4. **`mv 目录名称 新目录名称`:** 修改目录的名称(改) - 注意:mv的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。 + 注意:mv的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。 5. **`mv 目录名称 目录的新位置`:** 移动目录的位置---剪切(改) 注意:mv语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外mv与cp的结果不同,mv好像文件“搬家”,文件个数并未增加。而cp对文件进行复制,文件个数增加了。 6. **`cp -r 目录名称 目录拷贝的目标位置`:** 拷贝目录(改),-r代表递归拷贝 - 注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归 + 注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归 7. **`rm [-rf] 目录`:** 删除目录(删) - 注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包 + 注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包 -### 4.3 文件的操作命令(增删改查) +### 4.3 文件的操作命令(增删改查) 1. **`touch 文件名称`:** 文件的创建(增) 2. **`cat/more/less/tail 文件名称`** 文件的查看(查) - - **`cat`:** 只能显示最后一屏内容 - - **`more`:** 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看 - - **`less`:** 可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看 + - **`cat`:** 查看显示文件内容 + - **`more`:** 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看 + - **`less`:** 可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看 - **`tail-10` :** 查看文件的后10行,Ctrl+C结束 - 注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 + 注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 3. **`vim 文件`:** 修改文件的内容(改) vim编辑器是Linux中的强大组件,是vi编辑器的加强版,vim编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就可以了。 **在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤:** - vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) + vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) 4. **`rm -rf 文件`:** 删除文件(删) 同目录删除:熟记 `rm -rf` 文件 即可 @@ -183,7 +206,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g ### 4.5 Linux的权限命令 - 操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在Linux中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 **`ls -l`** 命令我们可以 查看某个目录下的文件或目录的权限 + 操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在Linux中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 **`ls -l`** 命令我们可以 查看某个目录下的文件或目录的权限 示例:在随意某个目录下`ls -l` @@ -200,7 +223,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g - d: 代表目录 - -: 代表文件 -- l: 代表链接(可以认为是window中的快捷方式) +- l: 代表软链接(可以认为是window中的快捷方式) **Linux中权限分为以下几种:** @@ -230,7 +253,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g | x | 可以使用cd进入目录 | - +**需要注意的是超级用户可以无视普通用户的权限,即使文件目录权限是000,依旧可以访问。** **在linux中的每个用户必须属于一个组,不能独立于组外。在linux中每个文件有所有者、所在组、其它组的概念。** - **所有者** @@ -304,7 +327,7 @@ passwd命令用于设置用户的认证信息,包括用户密码、密码过 - **`pwd`:** 显示当前所在位置 - **`grep 要搜索的字符串 要搜索的文件 --color`:** 搜索命令,--color代表高亮显示 -- **`ps -ef`/`ps aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括redis字符串的进程) +- **`ps -ef`/`ps -aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括redis字符串的进程),也可使用 `pgrep redis -a`。 注意:如果直接用ps((Process Status))命令,会显示所有进程的状态,通常结合grep命令查看某进程的状态。 - **`kill -9 进程的pid`:** 杀死进程(-9 表示强制终止。) @@ -314,6 +337,8 @@ passwd命令用于设置用户的认证信息,包括用户密码、密码过 - 查看当前系统的网卡信息:ifconfig - 查看与某台机器的连接情况:ping - 查看当前系统的端口使用:netstat -an +- **net-tools 和 iproute2 :** + `net-tools`起源于BSD的TCP/IP工具箱,后来成为老版本Linux内核中配置网络功能的工具。但自2001年起,Linux社区已经对其停止维护。同时,一些Linux发行版比如Arch Linux和CentOS/RHEL 7则已经完全抛弃了net-tools,只支持`iproute2`。linux ip命令类似于ifconfig,但功能更强大,旨在替代它。更多详情请阅读[如何在Linux中使用IP命令和示例](https://linoxide.com/linux-command/use-ip-command-linux) - **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定5分钟后关机,同时送出警告信息给登入用户。 - **`reboot`:** **`reboot`:** 重开机。**`reboot -w`:** 做个重开机的模拟(只有纪录并不会真的重开机)。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" index dda0ba8bb62..27b82c8bc70 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" @@ -60,7 +60,7 @@ MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索 ``` select * from user where name=xx and city=xx ; //可以命中索引 select * from user where name=xx ; // 可以命中索引 -select * from user where city=xx; // 法命中索引 +select * from user where city=xx; // 无法命中索引 ``` 这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的. @@ -109,4 +109,4 @@ ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` - 《Java工程师修炼之道》 - 《MySQL高性能书籍_第3版》 - https://juejin.im/post/5b55b842f265da0f9e589e79 - \ No newline at end of file + diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" index 6d7a484d658..44eb02aaa9d 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" @@ -47,7 +47,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去   **MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。 -   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 +   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 详细内容可以参考: @@ -87,20 +87,20 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 ![事务的特性](https://user-gold-cdn.xitu.io/2018/5/20/1637b08b98619455?w=312&h=305&f=png&s=22430) 1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 2. **一致性:** 执行事务前后,数据保持一致; - 3. **隔离性:** 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的; - 4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。 + 2. **一致性:** 执行事务前后,数据库从一个一致性状态转换到另一个一致性状态。 + 3. **隔离性:** 并发访问数据库时,一个用户的事物不被其他事务所干扰,各并发事务之间数据库是独立的; + 4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。 **为了达到上述事务特性,数据库定义了几种不同的事务隔离级别:** -- **READ_UNCOMMITTED(未授权读取):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** -- **READ_COMMITTED(授权读取):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **REPEATABLE_READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** +- **READ_UNCOMMITTED(未提交读):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** +- **READ_COMMITTED(提交读):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** +- **REPEATABLE_READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** - **SERIALIZABLE(串行):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 这里需要注意的是:**Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.** - 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。 + 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVCC(多版本并发控制),通过行的创建时间和行的过期时间来支持并发一致性读和回滚等特性。 详细内容可以参考: [可能是最漂亮的Spring事务管理详解](https://blog.csdn.net/qq_34337272/article/details/80394121) @@ -136,8 +136,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。; 2. **读/写分离:** 经典的数据库拆分方案,主库负责写,从库负责读; - 3. **缓存:** 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存; - 4. **垂直分区:** + 3 . **垂直分区:** **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。 @@ -148,7 +147,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 **垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; - 5. **水平分区:** + 4. **水平分区:** **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** @@ -157,7 +156,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 ![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119) - 水品拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水品拆分最好分库** 。 + 水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。 水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" index 41234cf55ac..5732c156825 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" @@ -162,7 +162,7 @@ redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大 3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 4. **allkeys-lru**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的). 5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰 -6. **no-enviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! +6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! **备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!** diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" index 8b8207f7f82..9889644f39c 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -167,7 +167,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 - 3. B*树 是B+树的变体,B*树分配新结点的概率比B+树要低,空间使用率更高; + 3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高; * ### 8 LSM 树 [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599) @@ -179,4 +179,12 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html) +## 图 + + + + +## BFS及DFS + +- [《使用BFS及DFS遍历树和图的思路及实现》](https://blog.csdn.net/Gene1994/article/details/85097507) diff --git "a/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" "b/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" new file mode 100644 index 00000000000..f975930f7ee --- /dev/null +++ "b/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" @@ -0,0 +1,47 @@ +> 本文是作者读 《大型网站技术架构》所做的思维导图,在这里分享给各位,公众号(JavaGuide)后台回复:“架构”。即可获得下面图片的源文件以及思维导图源文件! + + + +- [1. 大型网站架构演化](#1-大型网站架构演化) +- [2. 大型架构模式](#2-大型架构模式) +- [3. 大型网站核心架构要素](#3-大型网站核心架构要素) +- [4. 瞬时响应:网站的高性能架构](#4-瞬时响应网站的高性能架构) +- [5. 万无一失:网站的高可用架构](#5-万无一失网站的高可用架构) +- [6. 永无止境:网站的伸缩性架构](#6-永无止境网站的伸缩性架构) +- [7. 随机应变:网站的可扩展性架构](#7-随机应变网站的可扩展性架构) +- [8. 固若金汤:网站的安全机构](#8-固若金汤网站的安全机构) + + + + +### 1. 大型网站架构演化 + +![1. 大型网站架构演化](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/1%20%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84%E6%BC%94%E5%8C%96.png) + +### 2. 大型架构模式 + +![2. 大型架构模式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2%20%E5%A4%A7%E5%9E%8B%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F.png) + +### 3. 大型网站核心架构要素 + +![3. 大型网站核心架构要素](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3%20%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%A0%B8%E5%BF%83%E6%9E%B6%E6%9E%84%E8%A6%81%E7%B4%A0.png) + +### 4. 瞬时响应:网站的高性能架构 + +![4. 瞬时响应:网站的高性能架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/4%20%E7%9E%AC%E6%97%B6%E5%93%8D%E5%BA%94%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E9%AB%98%E6%80%A7%E8%83%BD%E6%9E%B6%E6%9E%84.png) + +### 5. 万无一失:网站的高可用架构 + +![5. 万无一失:网站的高可用架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/5%20%E4%B8%87%E6%97%A0%E4%B8%80%E5%A4%B1%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9E%B6%E6%9E%84.png) + +### 6. 永无止境:网站的伸缩性架构 + +![6. 永无止境:网站的伸缩性架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/6%20%E6%B0%B8%E6%97%A0%E6%AD%A2%E5%A2%83%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E4%BC%B8%E7%BC%A9%E6%80%A7%E6%9E%B6%E6%9E%84.png) + +### 7. 随机应变:网站的可扩展性架构 + +![7. 随机应变:网站的可扩展性架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/7%20%E9%9A%8F%E6%9C%BA%E5%BA%94%E5%8F%98%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E5%8F%AF%E6%89%A9%E5%B1%95%E6%9E%B6%E6%9E%84.png) + +### 8. 固若金汤:网站的安全机构 + +![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/8%20%E5%9B%BA%E8%8B%A5%E9%87%91%E6%B1%A4%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E5%AE%89%E5%85%A8%E6%9E%B6%E6%9E%84.png) diff --git "a/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" "b/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..415661fdeaa --- /dev/null +++ "b/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" @@ -0,0 +1,190 @@ +下面这些问题都是一线大厂的真实面试问题,不论是对你面试还是说拓宽知识面都很有帮助。之前发过一篇[8 张图读懂大型网站技术架构](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484416&idx=1&sn=6ced00d65491ef8fd33151bdfa8895c9&chksm=fd985261caefdb779412974a6a7207c93d0c2da5b28489afb74acd2fee28505daebbadb018ff&token=177958022&lang=zh_CN#rd) 可以作为不太了解大型网站系统技术架构朋友的入门文章。 + + + +- [1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量](#1-你使用过哪些组件或者方法来提升网站性能可用性以及并发量) +- [2. 设计高可用系统的常用手段](#2-设计高可用系统的常用手段) +- [3. 现代互联网应用系统通常具有哪些特点?](#3-现代互联网应用系统通常具有哪些特点) +- [4. 谈谈你对微服务领域的了解和认识](#4-谈谈你对微服务领域的了解和认识) +- [5. 谈谈你对 Dubbo 和 Spring Cloud 的认识\(两者关系\)](#5-谈谈你对-dubbo-和-spring-cloud-的认识两者关系) +- [6. 性能测试了解吗?说说你知道的性能测试工具?](#6-性能测试了解吗说说你知道的性能测试工具) +- [7. 对于一个单体应用系统,随着产品使用的用户越来越多,网站的流量会增加,最终单台服务器无法处理那么大的流量怎么办?](#7-对于一个单体应用系统随着产品使用的用户越来越多网站的流量会增加最终单台服务器无法处理那么大的流量怎么办) +- [8. 大表优化的常见手段](#8-大表优化的常见手段) +- [9. 在系统中使用消息队列能带来什么好处?](#9-在系统中使用消息队列能带来什么好处) + - [1) 通过异步处理提高系统性能](#1-通过异步处理提高系统性能) +- [2) 降低系统耦合性](#2-降低系统耦合性) +- [10. 说说自己对 CAP 定理,BASE 理论的了解](#10-说说自己对-cap-定理base-理论的了解) + - [CAP 定理](#cap-定理) + - [BASE 理论](#base-理论) +- [参考](#参考) + + + + +### 1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量 + +1. **提高硬件能力、增加系统服务器**。(当服务器增加到某个程度的时候系统所能提供的并发访问量几乎不变,所以不能根本解决问题) +2. **使用缓存**(本地缓存:本地可以使用JDK自带的 Map、Guava Cache.分布式缓存:Redis、Memcache.本地缓存不适用于提高系统并发量,一般是用处用在程序中。比如Spring是如何实现单例的呢?大家如果看过源码的话,应该知道,Spiring把已经初始过的变量放在一个Map中,下次再要使用这个变量的时候,先判断Map中有没有,这也就是系统中常见的单例模式的实现。) +3. **消息队列** (解耦+削峰+异步) +4. **采用分布式开发** (不同的服务部署在不同的机器节点上,并且一个服务也可以部署在多台机器上,然后利用 Nginx 负载均衡访问。这样就解决了单点部署(All In)的缺点,大大提高的系统并发量) +5. **数据库分库(读写分离)、分表(水平分表、垂直分表)** +6. **采用集群** (多台机器提供相同的服务) +7. **CDN 加速** (将一些静态资源比如图片、视频等等缓存到离用户最近的网络节点) +8. **浏览器缓存** +9. **使用合适的连接池**(数据库连接池、线程池等等) +10. **适当使用多线程进行开发。** + + +### 2. 设计高可用系统的常用手段 + +1. **降级:** 服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好; +2. **限流:** 防止恶意请求流量、恶意攻击,或者防止流量超出系统峰值; +3. **缓存:** 避免大量请求直接落到数据库,将数据库击垮; +4. **超时和重试机制:** 避免请求堆积造成雪崩; +5. **回滚机制:** 快速修复错误版本。 + + +### 3. 现代互联网应用系统通常具有哪些特点? + +1. 高并发,大流量; +2. 高可用:系统7×24小时不间断服务; +3. 海量数据:需要存储、管理海量数据,需要使用大量服务器; +4. 用户分布广泛,网络情况复杂:许多大型互联网都是为全球用户提供服务的,用户分布范围广,各地网络情况千差万别; +5. 安全环境恶劣:由于互联网的开放性,使得互联网更容易受到攻击,大型网站几乎每天都会被黑客攻击; +6. 需求快速变更,发布频繁:和传统软件的版本发布频率不同,互联网产品为快速适应市场,满足用户需求,其产品发布频率是极高的; +7. 渐进式发展:与传统软件产品或企业应用系统一开始就规划好全部的功能和非功能需求不同,几乎所有的大型互联网网站都是从一个小网站开始,渐进地发展起来。 + +### 4. 谈谈你对微服务领域的了解和认识 + +现在大公司都在用并且未来的趋势都是 Spring Cloud,而阿里开源的 Spring Cloud Alibaba 也是 Spring Cloud 规范的实现 。 + +我们通常把 Spring Cloud 理解为一系列开源组件的集合,但是 Spring Cloud并不是等同于 Spring Cloud Netflix 的 Ribbon、Feign、Eureka(停止更新)、Hystrix 这一套组件,而是抽象了一套通用的开发模式。它的目的是通过抽象出这套通用的模式,让开发者更快更好地开发业务。但是这套开发模式运行时的实际载体,还是依赖于 RPC、网关、服务发现、配置管理、限流熔断、分布式链路跟踪等组件的具体实现。 + +Spring Cloud Alibaba 是官方认证的新一套 Spring Cloud 规范的实现,Spring Cloud Alibaba 是一套国产开源产品集合,后续还会有中文 reference 和一些原理分析文章,所以,这对于国内的开发者是非常棒的一件事。阿里的这一举动势必会推动国内微服务技术的发展,因为在没有 Spring Cloud Alibaba 之前,我们的第一选择是 Spring Cloud Netflix,但是它们的文档都是英文的,出问题后排查也比较困难, 在国内并不是有特别多的人精通。Spring Cloud Alibaba 由阿里开源组件和阿里云产品组件两部分组成,其致力于提供微服务一站式解决方案,方便开发者通过 Spring Cloud 编程模型轻松开发微服务应用。 + +另外,Apache Dubbo Ecosystem 是围绕 Apache Dubbo 打造的微服务生态,是经过生产验证的微服务的最佳实践组合。在阿里巴巴的微服务解决方案中,Dubbo、Nacos 和 Sentinel,以及后续将开源的微服务组件,都是 Dubbo EcoSystem 的一部分。阿里后续也会将 Dubbo EcoSystem 集成到 Spring Cloud 的生态中。 + +### 5. 谈谈你对 Dubbo 和 Spring Cloud 的认识(两者关系) + +具体可以看公众号-阿里巴巴中间件的这篇文章:[独家解读:Dubbo Ecosystem - 从微服务框架到微服务生态](https://mp.weixin.qq.com/s/iNVctXw7tUGHhnF0hV84ww) + +Dubbo 与 Spring Cloud 并不是竞争关系,Dubbo 作为成熟的 RPC 框架,其易用性、扩展性和健壮性已得到业界的认可。未来 Dubbo 将会作为 Spring Cloud Alibaba 的 RPC 组件,并与 Spring Cloud 原生的 Feign 以及 RestTemplate 进行无缝整合,实现“零”成本迁移。 + +在阿里巴巴的微服务解决方案中,Dubbo、Nacos 和 Sentinel,以及后续将开源的微服务组件,都是 Dubbo EcoSystem 的一部分。我们后续也会将 Dubbo EcoSystem 集成到 Spring Cloud 的生态中。 + +### 6. 性能测试了解吗?说说你知道的性能测试工具? + +性能测试指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。性能测试是总称,通常细分为: + +1. **基准测试:** 在给系统施加较低压力时,查看系统的运行状况并记录相关数做为基础参考 +2. **负载测试:**是指对系统不断地增加压力或增加一定压力下的持续时间,直到系统的某项或多项性能指标达到安全临界值,例如某种资源已经达到饱和状态等 。此时继续加压,系统处理能力会下降。 +3. **压力测试:** 超过安全负载情况下,不断施加压力(增加并发请求),直到系统崩溃或无法处理任何请求,依此获得系统最大压力承受能力。 +4. **稳定性测试:** 被测试系统在特定硬件、软件、网络环境下,加载一定业务压力(模拟生产环境不同时间点、不均匀请求,呈波浪特性)运行一段较长时间,以此检测系统是否稳定。 + +后端程序员或者测试平常比较常用的测试工具是 JMeter(官网:[https://jmeter.apache.org/](https://jmeter.apache.org/))。Apache JMeter 是一款基于Java的压力测试工具(100%纯Java应用程序),旨在加载测试功能行为和测量性能。它最初被设计用于 Web 应用测试但后来扩展到其他测试领域。 + +### 7. 对于一个单体应用系统,随着产品使用的用户越来越多,网站的流量会增加,最终单台服务器无法处理那么大的流量怎么办? + +这个时候就要考虑扩容了。《亿级流量网站架构核心技术》这本书上面介绍到我们可以考虑下面几步来解决这个问题: + +- 第一步,可以考虑简单的扩容来解决问题。比如增加系统的服务器,提高硬件能力等等。 +- 第二步,如果简单扩容搞不定,就需要水平拆分和垂直拆分数据/应用来提升系统的伸缩性,即通过扩容提升系统负载能力。 +- 第三步,如果通过水平拆分/垂直拆分还是搞不定,那就需要根据现有系统特性,架构层面进行重构甚至是重新设计,即推倒重来。 + +对于系统设计,理想的情况下应支持线性扩容和弹性扩容,即在系统瓶颈时,只需要增加机器就可以解决系统瓶颈,如降低延迟提升吞吐量,从而实现扩容需求。 + +如果你想扩容,则支持水平/垂直伸缩是前提。在进行拆分时,一定要清楚知道自己的目的是什么,拆分后带来的问题如何解决,拆分后如果没有得到任何收益就不要为了 +拆而拆,即不要过度拆分,要适合自己的业务。 + +### 8. 大表优化的常见手段 + + 当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下: + +1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。; +2. **读/写分离:** 经典的数据库拆分方案,主库负责写,从库负责读; +3. **垂直分区:** **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。**简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。** 如下图所示,这样来说大家应该就更容易理解了。![](https://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015)**垂直拆分的优点:** 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。**垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; +4. **水平分区:** **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119)水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 + +**下面补充一下数据库分片的两种常见方案:** + +- **客户端代理:** **分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。 +- **中间件代理:** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。 + +### 9. 在系统中使用消息队列能带来什么好处? + +**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。** + +#### 1) 通过异步处理提高系统性能 +![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123) +如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。** + +通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: +![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550) +因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。 + +### 2) 降低系统耦合性 +我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和2.**分布式服务**。 + +> **先来简单说一下分布式服务:** + +目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c) + +> **再来谈我们的分布式消息队列:** + +我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 + +我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: +![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946) +**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。 + +消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 + +**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。** + +**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。 + +> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题! + +### 10. 说说自己对 CAP 定理,BASE 理论的了解 + +#### CAP 定理 + +![CAP定理](https://user-gold-cdn.xitu.io/2018/5/24/163912e973ecb93c?w=624&h=471&f=png&s=32984) +在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点: + +- **一致性(Consistence)** :所有节点访问同一份最新的数据副本 +- **可用性(Availability)**:每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据 +- **分区容错性(Partition tolerance)** : 分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。 + +CAP仅适用于原子读写的NOSQL场景中,并不适合数据库系统。现在的分布式系统具有更多特性比如扩展性、可用性等等,在进行系统设计和开发时,我们不应该仅仅局限在CAP问题上。 + +**注意:不是所谓的3选2(不要被网上大多数文章误导了):** + +大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在CAP理论诞生12年之后,CAP之父也在2012年重写了之前的论文。 + +**当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能2选1。也就是说当网络分区之后P是前提,决定了P之后才有C和A的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。** + +我在网上找了很多文章想看一下有没有文章提到这个不是所谓的3选2,用百度半天没找到了一篇,用谷歌搜索找到一篇比较不错的,如果想深入学习一下CAP就看这篇文章把,我这里就不多BB了:**《分布式系统之CAP理论》 :** [http://www.cnblogs.com/hxsyl/p/4381980.html](http://www.cnblogs.com/hxsyl/p/4381980.html) + + +#### BASE 理论 + +**BASE** 是 **Basically Available(基本可用)** 、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求。 + +**BASE理论的核心思想:** 即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。 + + +**BASE理论三要素:** + +![BASE理论三要素](https://user-gold-cdn.xitu.io/2018/5/24/163914806d9e15c6?w=612&h=461&f=png&s=39129) + +1. **基本可用:** 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。 比如: **①响应时间上的损失**:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒;**②系统功能上的损失**:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面; +2. **软状态:** 软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时; +3. **最终一致性:** 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 + +### 参考 + +- 《大型网站技术架构》 +- 《亿级流量网站架构核心技术》 +- 《Java工程师修炼之道》 +- https://www.cnblogs.com/puresoul/p/5456855.html diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" new file mode 100644 index 00000000000..4132144608c --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" @@ -0,0 +1,137 @@ + + +- [1. SSL 与 TLS](#1-ssl-%E4%B8%8E-tls) +- [2. 从网络协议的角度理解 HTTPS](#2-%E4%BB%8E%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E7%9A%84%E8%A7%92%E5%BA%A6%E7%90%86%E8%A7%A3-https) +- [3. 从密码学的角度理解 HTTPS](#3-%E4%BB%8E%E5%AF%86%E7%A0%81%E5%AD%A6%E7%9A%84%E8%A7%92%E5%BA%A6%E7%90%86%E8%A7%A3-https) + - [3.1. TLS 工作流程](#31-tls-%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B) + - [3.2. 密码基础](#32-%E5%AF%86%E7%A0%81%E5%9F%BA%E7%A1%80) + - [3.2.1. 伪随机数生成器](#321-%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8) + - [3.2.2. 消息认证码](#322-%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81) + - [3.2.3. 数字签名](#323-%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D) + - [3.2.4. 公钥密码](#324-%E5%85%AC%E9%92%A5%E5%AF%86%E7%A0%81) + - [3.2.5. 证书](#325-%E8%AF%81%E4%B9%A6) + - [3.2.6. 密码小结](#326-%E5%AF%86%E7%A0%81%E5%B0%8F%E7%BB%93) + - [3.3. TLS 使用的密码技术](#33-tls-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%AF%86%E7%A0%81%E6%8A%80%E6%9C%AF) + - [3.4. TLS 总结](#34-tls-%E6%80%BB%E7%BB%93) +- [4. RSA 简单示例](#4-rsa-%E7%AE%80%E5%8D%95%E7%A4%BA%E4%BE%8B) +- [5. 参考](#5-%E5%8F%82%E8%80%83) + + + +# 1. SSL 与 TLS + +SSL:(Secure Socket Layer) 安全套接层,于 1994 年由网景公司设计,并于 1995 年发布了 3.0 版本 +TLS:(Transport Layer Security)传输层安全性协议,是 IETF 在 SSL3.0 的基础上设计的协议 +以下全部使用 TLS 来表示 + +# 2. 从网络协议的角度理解 HTTPS + +![此图并不准确][1] +HTTP:HyperText Transfer Protocol 超文本传输协议 +HTTPS:Hypertext Transfer Protocol Secure 超文本传输安全协议 +TLS:位于 HTTP 和 TCP 之间的协议,其内部有 TLS握手协议、TLS记录协议 +HTTPS 经由 HTTP 进行通信,但利用 TLS 来保证安全,即 HTTPS = HTTP + TLS + +# 3. 从密码学的角度理解 HTTPS + +HTTPS 使用 TLS 保证安全,这里的“安全”分两部分,一是传输内容加密、二是服务端的身份认证 + +## 3.1. TLS 工作流程 + +![此图并不准确][2] +此为服务端单向认证,还有客户端/服务端双向认证,流程类似,只不过客户端也有自己的证书,并发送给服务器进行验证 + +## 3.2. 密码基础 + +### 3.2.1. 伪随机数生成器 + +为什么叫伪随机数,因为没有真正意义上的随机数,具体可以参考 Random/TheadLocalRandom +它的主要作用在于生成对称密码的秘钥、用于公钥密码生成秘钥对 + +### 3.2.2. 消息认证码 + +消息认证码主要用于验证消息的完整性与消息的认证,其中消息的认证指“消息来自正确的发送者” + +>消息认证码用于验证和认证,而不是加密 + +![消息认证码过程][3] + +1. 发送者与接收者事先共享秘钥 +2. 发送者根据发送消息计算 MAC 值 +3. 发送者发送消息和 MAC 值 +4. 接收者根据接收到的消息计算 MAC 值 +5. 接收者根据自己计算的 MAC 值与收到的 MAC 对比 +6. 如果对比成功,说明消息完整,并来自与正确的发送者 + +### 3.2.3. 数字签名 + +消息认证码的缺点在于**无法防止否认**,因为共享秘钥被 client、server 两端拥有,server 可以伪造 client 发送给自己的消息(自己给自己发送消息),为了解决这个问题,我们需要它们有各自的秘钥不被第二个知晓(这样也解决了共享秘钥的配送问题) + +![数字签名过程][4] + +>数字签名和消息认证码都**不是为了加密** +>可以将单向散列函数获取散列值的过程理解为使用 md5 摘要算法获取摘要的过程 + +使用自己的私钥对自己所认可的消息生成一个该消息专属的签名,这就是数字签名,表明我承认该消息来自自己 +注意:**私钥用于加签,公钥用于解签,每个人都可以解签,查看消息的归属人** + +### 3.2.4. 公钥密码 + +公钥密码也叫非对称密码,由公钥和私钥组成,它是最开始是为了解决秘钥的配送传输安全问题,即,我们不配送私钥,只配送公钥,私钥由本人保管 +它与数字签名相反,公钥密码的私钥用于解密、公钥用于加密,每个人都可以用别人的公钥加密,但只有对应的私钥才能解开密文 +client:明文 + 公钥 = 密文 +server:密文 + 私钥 = 明文 +注意:**公钥用于加密,私钥用于解密,只有私钥的归属者,才能查看消息的真正内容** + +### 3.2.5. 证书 + +证书:全称公钥证书(Public-Key Certificate, PKC),里面保存着归属者的基本信息,以及证书过期时间、归属者的公钥,并由认证机构(Certification Authority, **CA**)施加数字签名,表明,某个认证机构认定该公钥的确属于此人 + +>想象这个场景:你想在支付宝页面交易,你需要支付宝的公钥进行加密通信,于是你从百度上搜索关键字“支付宝公钥”,你获得了支什宝的公钥,这个时候,支什宝通过中间人攻击,让你访问到了他们支什宝的页面,最后你在这个支什宝页面完美的使用了支什宝的公钥完成了与支什宝的交易 +>![证书过程][5] + +在上面的场景中,你可以理解支付宝证书就是由支付宝的公钥、和给支付宝颁发证书的企业的数字签名组成 +任何人都可以给自己或别人的公钥添加自己的数字签名,表明:我拿我的尊严担保,我的公钥/别人的公钥是真的,至于信不信那是另一回事了 + +### 3.2.6. 密码小结 + +| 密码 | 作用 | 组成 | +| :-- | :-- | :-- | +| 消息认证码 | 确认消息的完整、并对消息的来源认证 | 共享秘钥+消息的散列值 | +| 数字签名 | 对消息的散列值签名 | 公钥+私钥+消息的散列值 | +| 公钥密码 | 解决秘钥的配送问题 | 公钥+私钥+消息 | +| 证书 | 解决公钥的归属问题 | 公钥密码中的公钥+数字签名 | + +## 3.3. TLS 使用的密码技术 + +1. 伪随机数生成器:秘钥生成随机性,更难被猜测 +2. 对称密码:对称密码使用的秘钥就是由伪随机数生成,相较于非对称密码,效率更高 +3. 消息认证码:保证消息信息的完整性、以及验证消息信息的来源 +4. 公钥密码:证书技术使用的就是公钥密码 +5. 数字签名:验证证书的签名,确定由真实的某个 CA 颁发 +6. 证书:解决公钥的真实归属问题,降低中间人攻击概率 + +## 3.4. TLS 总结 + +TLS 是一系列密码工具的框架,作为框架,它也是非常的灵活,体现在每个工具套件它都可以替换,即:客户端与服务端之间协商密码套件,从而更难的被攻破,例如使用不同方式的对称密码,或者公钥密码、数字签名生成方式、单向散列函数技术的替换等 + +# 4. RSA 简单示例 + +RSA 是一种公钥密码算法,我们简单的走一遍它的加密解密过程 +加密算法:密文 = (明文^E) mod N,其中公钥为{E,N},即”求明文的E次方的对 N 的余数“ +解密算法:明文 = (密文^D) mod N,其中秘钥为{D,N},即”求密文的D次方的对 N 的余数“ +例:我们已知公钥为{5,323},私钥为{29,323},明文为300,请写出加密和解密的过程: +>加密:密文 = 123 ^ 5 mod 323 = 225 +>解密:明文 = 225 ^ 29 mod 323 = [[(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 4) mod 323]] mod 323 = (4 * 4 * 4 * 4 * 4 * 290) mod 323 = 123 + +# 5. 参考 + +1. SSL加密发生在哪里: +2. TLS工作流程: +3. 《图解密码技术》: 豆瓣评分 9.5 + +[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E4%B8%83%E5%B1%82.png +[2]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/tls%E6%B5%81%E7%A8%8B.png +[3]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81%E8%BF%87%E7%A8%8B.png +[4]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E8%BF%87%E7%A8%8B.png +[5]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/dns%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB.png \ No newline at end of file diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" new file mode 100644 index 00000000000..5cc6dc1b14a --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" @@ -0,0 +1,282 @@ +本文是作者根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。如果不懂 Dubbo 的使用的话,可以参考我的这篇文章[《超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1902169190&lang=zh_CN#rd) + +Dubbo 官网:http://dubbo.apache.org/zh-cn/index.html + +Dubbo 中文文档: http://dubbo.apache.org/zh-cn/index.html + + + +- [一 重要的概念](#一-重要的概念) + - [1.1 什么是 Dubbo?](#11-什么是-dubbo) + - [1.2 什么是 RPC?RPC原理是什么?](#12-什么是-rpcrpc原理是什么) + - [1.3 为什么要用 Dubbo?](#13-为什么要用-dubbo) + - [1.4 什么是分布式?](#14-什么是分布式) + - [1.5 为什么要分布式?](#15-为什么要分布式) +- [二 Dubbo 的架构](#二-dubbo-的架构) + - [2.1 Dubbo 的架构图解](#21-dubbo-的架构图解) + - [2.2 Dubbo 工作原理](#22-dubbo-工作原理) +- [三 Dubbo 的负载均衡策略](#三-dubbo-的负载均衡策略) + - [3.1 先来解释一下什么是负载均衡](#31-先来解释一下什么是负载均衡) + - [3.2 再来看看 Dubbo 提供的负载均衡策略](#32-再来看看-dubbo-提供的负载均衡策略) + - [3.2.1 Random LoadBalance\(默认,基于权重的随机负载均衡机制\)](#321-random-loadbalance默认基于权重的随机负载均衡机制) + - [3.2.2 RoundRobin LoadBalance\(不推荐,基于权重的轮询负载均衡机制\)](#322-roundrobin-loadbalance不推荐基于权重的轮询负载均衡机制) + - [3.2.3 LeastActive LoadBalance](#323-leastactive-loadbalance) + - [3.2.4 ConsistentHash LoadBalance](#324-consistenthash-loadbalance) + - [3.3 配置方式](#33-配置方式) +- [四 zookeeper宕机与dubbo直连的情况](#四-zookeeper宕机与dubbo直连的情况) + + + + +## 一 重要的概念 + +### 1.1 什么是 Dubbo? + +Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 + +Dubbo 目前已经有接近 23k 的 Star ,Dubbo的Github 地址:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo) 。 另外,在开源中国举行的2018年度最受欢迎中国开源软件这个活动的评选中,Dubbo 更是凭借其超高人气仅次于 vue.js 和 ECharts 获得第三名的好成绩。 + +Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。 + +**我们上面说了 Dubbo 实际上是 RPC 框架,那么什么是 RPC呢?** + +### 1.2 什么是 RPC?RPC原理是什么? + +**什么是 RPC?** + +RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。 + +**RPC原理是什么?** + +我这里这是简单的提一下。详细内容可以查看下面这篇文章: + +[http://www.importnew.com/22003.html](http://www.importnew.com/22003.html) + +![RPC原理图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/37345851.jpg) + + +1. 服务消费方(client)调用以本地调用方式调用服务; +2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; +3. client stub找到服务地址,并将消息发送到服务端; +4. server stub收到消息后进行解码; +5. server stub根据解码结果调用本地的服务; +6. 本地服务执行并将结果返回给server stub; +7. server stub将返回结果打包成消息并发送至消费方; +8. client stub接收到消息,并进行解码; +9. 服务消费方得到最终结果。 + +下面再贴一个网上的时序图: + +![RPC原理时序图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/32527396.jpg) + +**说了这么多,我们为什么要用 Dubbo 呢?** + +### 1.3 为什么要用 Dubbo? + +Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面向服务的架构(Service Oriented Architecture),也就是把工程按照业务逻辑拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。SOA架构中有两个主要角色:服务提供者(Provider)和服务使用者(Consumer)。 + +![为什么要用 Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/43050183.jpg) + +**如果你要开发分布式程序,你也可以直接基于 HTTP 接口进行通信,但是为什么要用 Dubbo呢?** + +我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo: + +1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务 +2. **服务调用链路生成**——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。 +3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。 +4. **服务降级**——某个服务挂掉之后调用备用服务 + +另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。 + +**我们刚刚提到了分布式这个概念,下面再给大家介绍一下什么是分布式?为什么要分布式?** + +### 1.4 什么是分布式? + +分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。 + +### 1.5 为什么要分布式? + +从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。 + +另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢? + +## 二 Dubbo 的架构 + +### 2.1 Dubbo 的架构图解 + +![Dubbo 架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/46816446.jpg) + +**上述节点简单说明:** + +- **Provider:** 暴露服务的服务提供方 +- **Consumer:** 调用远程服务的服务消费方 +- **Registry:** 服务注册与发现的注册中心 +- **Monitor:** 统计服务的调用次数和调用时间的监控中心 +- **Container:** 服务运行容器 + +**调用关系说明:** + +1. 服务容器负责启动,加载,运行服务提供者。 +2. 服务提供者在启动时,向注册中心注册自己提供的服务。 +3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 +4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 +5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 +6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 + +**重要知识点总结:** + +- **注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小** +- **监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示** +- **注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外** +- **注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者** +- **注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表** +- **注册中心和监控中心都是可选的,服务消费者可以直连服务提供者** +- **服务提供者无状态,任意一台宕掉后,不影响使用** +- **服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复** + + + + + +### 2.2 Dubbo 工作原理 + + +![Dubbo 工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/64702923.jpg) + +图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。 + +**各层说明**: + +- 第一层:**service层**,接口层,给服务提供者和消费者来实现的 +- 第二层:**config层**,配置层,主要是对dubbo进行各种配置的 +- 第三层:**proxy层**,服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton +- 第四层:**registry层**,服务注册层,负责服务的注册与发现 +- 第五层:**cluster层**,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 +- 第六层:**monitor层**,监控层,对rpc接口的调用次数和调用时间进行监控 +- 第七层:**protocol层**,远程调用层,封装rpc调用 +- 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步 +- 第九层:**transport层**,网络传输层,抽象mina和netty为统一接口 +- 第十层:**serialize层**,数据序列化层。网络传输需要。 + + +## 三 Dubbo 的负载均衡策略 + +### 3.1 先来解释一下什么是负载均衡 + +**先来个官方的解释。** + +> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件 + +**上面讲的大家可能不太好理解,再用通俗的话给大家说一下。** + +比如我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。 + +### 3.2 再来看看 Dubbo 提供的负载均衡策略 + +在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。可以自行扩展负载均衡策略,参见:[负载均衡扩展](https://dubbo.gitbooks.io/dubbo-dev-book/content/impls/load-balance.html)。 + +备注:下面的图片来自于:尚硅谷2018Dubbo 视频。 + + +#### 3.2.1 Random LoadBalance(默认,基于权重的随机负载均衡机制) + +- **随机,按权重设置随机概率。** +- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 + +![基于权重的随机负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/77722327.jpg) + + + +#### 3.2.2 RoundRobin LoadBalance(不推荐,基于权重的轮询负载均衡机制) + +- 轮循,按公约后的权重设置轮循比率。 +- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 + +![基于权重的轮询负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/97933247.jpg) + +#### 3.2.3 LeastActive LoadBalance + +- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 +- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 + +#### 3.2.4 ConsistentHash LoadBalance + +- **一致性 Hash,相同参数的请求总是发到同一提供者。(如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。)** +- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 +- 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing +- 缺省只对第一个参数 Hash,如果要修改,请配置 `` +- 缺省用 160 份虚拟节点,如果要修改,请配置 `` + +### 3.3 配置方式 + +**xml 配置方式** + +服务端服务级别 + +```java + +``` +客户端服务级别 + +```java + +``` + +服务端方法级别 + +```java + + + +``` + +客户端方法级别 + +```java + + + +``` + +**注解配置方式:** + +消费方基于基于注解的服务级别配置方式: + +```java +@Reference(loadbalance = "roundrobin") +HelloService helloService; +``` + +## 四 zookeeper宕机与dubbo直连的情况 + +zookeeper宕机与dubbo直连的情况在面试中可能会被经常问到,所以要引起重视。 + +在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种提现。 + +**dubbo的健壮性表现:** + +1. 监控中心宕掉不影响使用,只是丢失部分采样数据 +2. 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务 +3. 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 +4. 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯 +5. 服务提供者无状态,任意一台宕掉后,不影响使用 +5. 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复 + +我们前面提到过:注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。所以,我们可以完全可以绕过注册中心——采用 **dubbo 直连** ,即在服务消费方配置服务提供方的位置信息。 + + +**xml配置方式:** + +```xml + +``` + +**注解方式:** + +```java + @Reference(url = "127.0.0.1:20880") + HelloService helloService; +``` + + + diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" new file mode 100644 index 00000000000..e90a129a9a3 --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" @@ -0,0 +1,152 @@ + + +- [消息队列其实很简单](#消息队列其实很简单) + - [一 什么是消息队列](#一-什么是消息队列) + - [二 为什么要用消息队列](#二-为什么要用消息队列) + - [\(1\) 通过异步处理提高系统性能(削峰、减少响应所需时间)](#1-通过异步处理提高系统性能削峰减少响应所需时间) + - [\(2\) 降低系统耦合性](#2-降低系统耦合性) + - [三 使用消息队列带来的一些问题](#三-使用消息队列带来的一些问题) + - [四 JMS VS AMQP](#四-jms-vs-amqp) + - [4.1 JMS](#41-jms) + - [4.1.1 JMS 简介](#411-jms-简介) + - [4.1.2 JMS两种消息模型](#412-jms两种消息模型) + - [4.1.3 JMS 五种不同的消息正文格式](#413-jms-五种不同的消息正文格式) + - [4.2 AMQP](#42-amqp) + - [4.3 JMS vs AMQP](#43-jms-vs-amqp) + - [五 常见的消息队列对比](#五-常见的消息队列对比) + + + + +# 消息队列其实很简单 + +  “RabbitMQ?”“Kafka?”“RocketMQ?”...在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。 + +## 一 什么是消息队列 + +  我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ,我们后面会一一对比这些消息队列。 + +  另外,我们知道队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。比如生产者发送消息1,2,3...对于消费者就会按照1,2,3...的顺序来消费。但是偶尔也会出现消息被消费的顺序不对的情况,比如某个消息消费失败又或者一个 queue 多个consumer 也会导致消息被消费的顺序不对,我们一定要保证消息被消费的顺序正确。 + +  除了上面说的消息消费顺序的问题,使用消息队列,我们还要考虑如何保证消息不被重复消费?如何保证消息的可靠性传输(如何处理消息丢失的问题)?......等等问题。所以说使用消息队列也不是十全十美的,使用它也会让系统可用性降低、复杂度提高,另外需要我们保障一致性等问题。 + +## 二 为什么要用消息队列 + +  我觉得使用消息队列主要有两点好处:1.通过异步处理提高系统性能(削峰、减少响应所需时间);2.降低系统耦合性。如果在面试的时候你被面试官问到这个问题的话,一般情况是你在你的简历上涉及到消息队列这方面的内容,这个时候推荐你结合你自己的项目来回答。 + + +  《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。 + +### (1) 通过异步处理提高系统性能(削峰、减少响应所需时间) + +![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123) +  如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。** + +  通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: +![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550) + +  因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。 + +### (2) 降低系统耦合性 + +  我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 + +  我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: + +![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946) + +  **消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。 + +  消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 + +  **另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。** + +**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。**除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。** 另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了 5 种消息模型。 + +## 三 使用消息队列带来的一些问题 + +- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了! +- **系统复杂性提高:** 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题! +- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了! + +## 四 JMS VS AMQP + +### 4.1 JMS + +#### 4.1.1 JMS 简介 + +  JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范**,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 + +**ActiveMQ 就是基于 JMS 规范实现的。** + +#### 4.1.2 JMS两种消息模型 + +①点到点(P2P)模型 + +![点到点(P2P)模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7185572ca37d?w=575&h=135&f=gif&s=8530) +  使用**队列(Queue)**作为消息通信载体;满足**生产者与消费者模式**,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。) + +② 发布/订阅(Pub/Sub)模型 + + ![发布/订阅(Pub/Sub)模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7187c268eaa5?w=402&h=164&f=gif&s=15492) +  发布订阅模型(Pub/Sub) 使用**主题(Topic)**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。 + +#### 4.1.3 JMS 五种不同的消息正文格式 + +  JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。 + +- StreamMessage -- Java原始值的数据流 +- MapMessage--一套名称-值对 +- TextMessage--一个字符串对象 +- ObjectMessage--一个序列化的 Java对象 +- BytesMessage--一个字节的数据流 + + +### 4.2 AMQP + +  ​ AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。 + +**RabbitMQ 就是基于 AMQP 协议实现的。** + + + +### 4.3 JMS vs AMQP + + +|对比方向| JMS | AMQP | +| :-------- | --------:| :--: | +| 定义| Java API | 协议 | +| 跨语言 | 否 | 是 | +| 跨平台 | 否 | 是 | +| 支持消息类型 | 提供两种消息模型:①Peer-2-Peer;②Pub/sub| 提供了五种消息模型:①direct exchange;②fanout exchange;③topic change;④headers exchange;⑤system exchange。本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分;| +|支持消息类型| 支持多种消息类型 ,我们在上面提到过| byte[](二进制)| + +**总结:** + +- AMQP 为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。在 Java 体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而AMQP天然具有跨平台、跨语言特性。 +- JMS 支持TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。 +- 由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。 + + +## 五 常见的消息队列对比 + + + +对比方向 |概要 +-------- | --- + 吞吐量| 万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。 +可用性| 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 +时效性| RabbitMQ 基于erlang开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。 +功能支持| 除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 +消息丢失| ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。 + + +**总结:** + +- ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。 +- RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 +- RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的 +- kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 + + +参考:《Java工程师面试突击第1季-中华石杉老师》 diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" new file mode 100644 index 00000000000..7b1508f456c --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" @@ -0,0 +1,320 @@ + + +- [一文搞懂 RabbitMQ 的重要概念以及安装](#一文搞懂-rabbitmq-的重要概念以及安装) + - [一 RabbitMQ 介绍](#一-rabbitmq-介绍) + - [1.1 RabbitMQ 简介](#11-rabbitmq-简介) + - [1.2 RabbitMQ 核心概念](#12-rabbitmq-核心概念) + - [1.2.1 Producer(生产者) 和 Consumer(消费者)](#121-producer生产者-和-consumer消费者) + - [1.2.2 Exchange(交换器)](#122-exchange交换器) + - [1.2.3 Queue(消息队列)](#123-queue消息队列) + - [1.2.4 Broker(消息中间件的服务节点)](#124-broker消息中间件的服务节点) + - [1.2.5 Exchange Types(交换器类型)](#125-exchange-types交换器类型) + - [① fanout](#①-fanout) + - [② direct](#②-direct) + - [③ topic](#③-topic) + - [④ headers(不推荐)](#④-headers不推荐) + - [二 安装 RabbitMq](#二-安装-rabbitmq) + - [2.1 安装 erlang](#21-安装-erlang) + - [2.2 安装 RabbitMQ](#22-安装-rabbitmq) + + + +# 一文搞懂 RabbitMQ 的重要概念以及安装 + +## 一 RabbitMQ 介绍 + +这部分参考了 《RabbitMQ实战指南》这本书的第 1 章和第 2 章。 + +### 1.1 RabbitMQ 简介 + +RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。 + +RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点: + +- **可靠性:** RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。 +- **灵活的路由:** 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们将 RabbitMQ 核心概念的时候详细介绍到。 +- **扩展性:** 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。 +- **高可用性:** 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。 +- **支持多种协议:** RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。 +- **多语言客户端:** RabbitMQ几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript等。 +- **易用的管理界面:** RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。 +- **插件机制:** RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。 + +### 1.2 RabbitMQ 核心概念 + +RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,RabbitMQ就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ 模型更像是一种交换机模型。 + +下面再来看看图1—— RabbitMQ 的整体模型架构。 + +![图1-RabbitMQ 的整体模型架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/96388546.jpg) + +下面我会一一介绍上图中的一些概念。 + +#### 1.2.1 Producer(生产者) 和 Consumer(消费者) + +- **Producer(生产者)** :生产消息的一方(邮件投递者) +- **Consumer(消费者)** :消费消息的一方(邮件收件人) + +消息一般由 2 部分组成:**消息头**(或者说是标签 Label)和 **消息体**。消息体也可以称为 payLoad ,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。 + +#### 1.2.2 Exchange(交换器) + +在 RabbitMQ 中,消息并不是直接被投递到 **Queue(消息队列)** 中的,中间还必须经过 **Exchange(交换器)** 这一层,**Exchange(交换器)** 会把我们的消息分配到对应的 **Queue(消息队列)** 中。 + +**Exchange(交换器)** 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到,或许会返回给 **Producer(生产者)** ,或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。 + +**RabbitMQ 的 Exchange(交换器) 有4种类型,不同的类型对应着不同的路由策略**:**direct(默认)**,**fanout**, **topic**, 和 **headers**,不同类型的Exchange转发消息的策略有所区别。这个会在介绍 **Exchange Types(交换器类型)** 的时候介绍到。 + +Exchange(交换器) 示意图如下: + +![Exchange(交换器) 示意图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/24007899.jpg) + +生产者将消息发给交换器的时候,一般会指定一个 **RoutingKey(路由键)**,用来指定这个消息的路由规则,而这个 **RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效**。 + +RabbitMQ 中通过 **Binding(绑定)** 将 **Exchange(交换器)** 与 **Queue(消息队列)** 关联起来,在绑定的时候一般会指定一个 **BindingKey(绑定建)** ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。 + +Binding(绑定) 示意图: + +![Binding(绑定) 示意图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/70553134.jpg) + +生产者将消息发送给交换器时,需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。 + +#### 1.2.3 Queue(消息队列) + +**Queue(消息队列)** 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 + +**RabbitMQ** 中消息只能存储在 **队列** 中,这一点和 **Kafka** 这种消息中间件相反。Kafka 将消息存储在 **topic(主题)** 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。 + +**多个消费者可以订阅同一个队列**,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。 + +**RabbitMQ** 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。 + +#### 1.2.4 Broker(消息中间件的服务节点) + +对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。 + +下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。 + +![消息队列的运转过程](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/67952922.jpg) + +这样图1中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了,下面再来介绍一下 **Exchange Types(交换器类型)** 。 + +#### 1.2.5 Exchange Types(交换器类型) + +RabbitMQ 常用的 Exchange Type 有 **fanout**、**direct**、**topic**、**headers** 这四种(AMQP规范里还提到两种 Exchange Type,分别为 system 与 自定义,这里不予以描述)。 + +##### ① fanout + +fanout 类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息。 + +##### ② direct + +direct 类型的Exchange路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。 + +![direct 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/37008021.jpg) + +以上图为例,如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info”或者"debug”,消息只会路由到Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。 + +direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。 + +##### ③ topic + +前面讲到direct类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定: + +- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”; +- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串; +- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“#”用于匹配一个单词,“#”用于匹配多规格单词(可以是零个)。 + +![topic 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/73843.jpg) + +以上图为例: + +- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queuel 和 Queue2; +- 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中; +- 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中; +- 路由键为 “java.rabbitmq.demo” 的消息只会路由到Queuel中; +- 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。 + +##### ④ headers(不推荐) + +headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ会获取到该消息的 headers(也是一个键值对的形式)'对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。 + +## 二 安装 RabbitMq + +通过 Docker 安装非常方便,只需要几条命令就好了,我这里是只说一下常规安装方法。 + +前面提到了 RabbitMQ 是由 Erlang语言编写的,也正因如此,在安装RabbitMQ 之前需要安装 Erlang。 + +### 2.1 安装 erlang + +**1 下载 erlang 安装包** + +在官网下载然后上传到 Linux 上或者直接使用下面的命令下载对应的版本。 + +```shell +[root@SnailClimb local]#wget http://erlang.org/download/otp_src_19.3.tar.gz +``` + +erlang 官网下载:[http://www.erlang.org/downloads](http://www.erlang.org/downloads) + + **2 解压 erlang 安装包** + +```shell +[root@SnailClimb local]#tar -xvzf otp_src_19.3.tar.gz +``` + +**3 删除 erlang 安装包** + +```shell +[root@SnailClimb local]#rm -rf otp_src_19.3.tar.gz +``` + +**4 安装 erlang 的依赖工具** + +```shell +[root@SnailClimb local]#yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel +``` + +**5 进入erlang 安装包解压文件对 erlang 进行安装环境的配置** + +新建一个文件夹 + +```shell +[root@SnailClimb local]# mkdir erlang +``` + +对 erlang 进行安装环境的配置 + +```shell +[root@SnailClimb otp_src_19.3]# +./configure --prefix=/usr/local/erlang --without-javac +``` + +**6 编译安装** + +```shell +[root@SnailClimb otp_src_19.3]# +make && make install +``` + +**7 验证一下 erlang 是否安装成功了** + +```shell +[root@SnailClimb otp_src_19.3]# ./bin/erl +``` +运行下面的语句输出“hello world” + +```erlang + io:format("hello world~n", []). +``` +![输出“hello world”](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/49570541.jpg) + +大功告成,我们的 erlang 已经安装完成。 + +**8 配置 erlang 环境变量** + +```shell +[root@SnailClimb etc]# vim profile +``` + +追加下列环境变量到文件末尾 + +```shell +#erlang +ERL_HOME=/usr/local/erlang +PATH=$ERL_HOME/bin:$PATH +export ERL_HOME PATH +``` + +运行下列命令使配置文件`profile`生效 + +```shell +[root@SnailClimb etc]# source /etc/profile +``` + +输入 erl 查看 erlang 环境变量是否配置正确 + +```shell +[root@SnailClimb etc]# erl +``` + +![输入 erl 查看 erlang 环境变量是否配置正确](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/62504246.jpg) + +### 2.2 安装 RabbitMQ + +**1. 下载rpm** + +```shell +wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.8/rabbitmq-server-3.6.8-1.el7.noarch.rpm +``` +或者直接在官网下载 + +https://www.rabbitmq.com/install-rpm.html[enter link description here](https://www.rabbitmq.com/install-rpm.html) + +**2. 安装rpm** + +```shell +rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc +``` +紧接着执行: + +```shell +yum install rabbitmq-server-3.6.8-1.el7.noarch.rpm +``` +中途需要你输入"y"才能继续安装。 + +**3 开启 web 管理插件** + +```shell +rabbitmq-plugins enable rabbitmq_management +``` + +**4 设置开机启动** + +```shell +chkconfig rabbitmq-server on +``` + +**4. 启动服务** + +```shell +service rabbitmq-server start +``` + +**5. 查看服务状态** + +```shell +service rabbitmq-server status +``` + +**6. 访问 RabbitMQ 控制台** + +浏览器访问:http://你的ip地址:15672/ + +默认用户名和密码: guest/guest;但是需要注意的是:guestuest用户只是被容许从localhost访问。官网文档描述如下: + +```shell +“guest” user can only connect via localhost +``` + +**解决远程访问 RabbitMQ 远程访问密码错误** + +新建用户并授权 + +```shell +[root@SnailClimb rabbitmq]# rabbitmqctl add_user root root +Creating user "root" ... +[root@SnailClimb rabbitmq]# rabbitmqctl set_user_tags root administrator + +Setting tags for user "root" to [administrator] ... +[root@SnailClimb rabbitmq]# +[root@SnailClimb rabbitmq]# rabbitmqctl set_permissions -p / root ".*" ".*" ".*" +Setting permissions for user "root" in vhost "/" ... + +``` + +再次访问:http://你的ip地址:15672/ ,输入用户名和密码:root root + +![RabbitMQ控制台](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/45835332.jpg) + + diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" index 2df02d0312c..7840d844d73 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" @@ -55,7 +55,7 @@ [《消息队列深入解析》](https://blog.csdn.net/qq_34337272/article/details/80029918) -当前使用较多的消息队列有ActiveMQ(性能差,不推荐使用)、RabbitMQ、RocketMQ、Kafka等等,我们之前提高的redis数据库也可以实现消息队列,不过不推荐,redis本身设计就不是用来做消息队列的。 +当前使用较多的消息队列有ActiveMQ(性能差,不推荐使用)、RabbitMQ、RocketMQ、Kafka等等,我们之前提到的redis数据库也可以实现消息队列,不过不推荐,redis本身设计就不是用来做消息队列的。 - **ActiveMQ:** ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的JMSProvider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。 @@ -80,7 +80,7 @@ [《十分钟入门RocketMQ》](http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/) (阿里中间件团队博客) -- **Kafka**:Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统,Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。 +- **Kafka**:Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统(现在官方的描述是“一个分布式流平台”),Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。 具体可以参考: diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/JavaInterviewGithub.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/JavaInterviewGithub.md" new file mode 100644 index 00000000000..4901f88932d --- /dev/null +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/JavaInterviewGithub.md" @@ -0,0 +1,78 @@ +最近浏览 Github ,收藏了一些还算不错的 Java面试/学习相关的仓库,分享给大家,希望对你有帮助。我暂且按照目前的 Star 数量来排序。 + +本文由 SnailClimb 整理,如需转载请联系作者。 + +### 1. interviews + +- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md) +- star: 31k +- 介绍: 软件工程技术面试个人指南。 +- 概览: + + ![interviews](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/47663247.jpg) + +### 2. JCSprout + +- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout) +- star: 17.7k +- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。 +- 概览: + + ![ JCSprout](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/85903384.jpg) + +### 3. JavaGuide + +- Github地址: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) +- star: 17.4k +- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 +- 概览: + + ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) + +### 4. technology-talk + +- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk) +- star: 4.2k +- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。 + +### 5. fullstack-tutorial + +- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial) +- star: 2.8k +- 介绍: Full Stack Developer Tutorial,后台技术栈/全栈开发/架构师之路,秋招/春招/校招/面试。 from zero to hero。 +- 概览: + + ![fullstack-tutorial](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/67104534.jpg) + +### 6. java-bible + +- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible) +- star: 1.9k +- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。 +- 概览: + + ![ java-bible](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/90223588.jpg) + +### 7. EasyJob + +- Github地址:[https://github.com/it-interview/EasyJob](https://github.com/it-interview/EasyJob) +- star: 1.9k +- 介绍: 互联网求职面试题、知识点和面经整理。 + +### 8. advanced-java + +- Github地址:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) +- star: 1k +- 介绍: 互联网 Java 工程师进阶知识完全扫盲 + +### 9. 3y + +- Github地址:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y) +- star: 0.4 k +- 介绍: Java 知识整合。 + +除了这九个仓库,再推荐几个不错的学习方向的仓库给大家。 + +1. Star 数高达 4w+的 CS 笔记-CS-Notes:[https://github.com/CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes) +2. 后端(尤其是Java)程序员的 Linux 学习仓库-Linux-Tutorial:[https://github.com/judasn/Linux-Tutorial](https://github.com/judasn/Linux-Tutorial)( Star:4.6k) +3. 两个算法相关的仓库,刷 Leetcode 的小伙伴必备:①awesome-java-leetcode:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode);②LintCode:[https://github.com/awangdev/LintCode](https://github.com/awangdev/LintCode) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/books.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/books.md" new file mode 100644 index 00000000000..34f495fd798 --- /dev/null +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/books.md" @@ -0,0 +1,66 @@ + +### 核心基础知识 + +- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 +- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 +- [《数据结构与算法分析:C语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。 +- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! +- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 + + + + +### Java相关 + +- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 +- [《Head First Java.第二版》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 +- [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/): Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。 +- [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/): 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。 +- [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。 +- [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.1,3.2K+人评价):这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 +- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 +- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 +- [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/): 很杂,我只看了前面几章,不太推荐阅读。 +- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 +- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! +- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 + +### JavaWeb相关 + +- [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/): 感觉还行,涉及的东西也蛮多。 +- [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3 +,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 +- [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/)(已过时):当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。 +- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 +- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) +- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 +- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 +- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 +- [《RabbitMQ实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一 +- [《Spring Cloud微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 +- [《第一本Docker书》](https://book.douban.com/subject/26780404/):Docker入门书籍! + +### 操作系统 + +- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 + +### 架构相关 + +- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 +- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 +- [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/):很一般的书籍,我就是当做课后图书来阅读的。 + +### 代码优化 + +- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 + +### 课外书籍 + +- 《追风筝的人》(推荐) +- 《穆斯林的葬礼》 (推荐) +- 《三体》 (推荐) +- 《活着——余华》 (推荐) + + + + diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/interviewPrepare.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/interviewPrepare.md" new file mode 100644 index 00000000000..c99ca1c156e --- /dev/null +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/interviewPrepare.md" @@ -0,0 +1,74 @@ +这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。 + +不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。 + +### 1 如何获取大厂面试机会? + +**在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。** + +1. **招聘人数** :秋招多于春招 ; +2. **招聘时间** : 秋招一般7月左右开始,大概一直持续到10月底。但是大厂(如BAT)都会早开始早结束,所以一定要把握好时间。春招最佳时间为3月,次佳时间为4月,进入5月基本就不会再有春招了(金三银四)。 +3. **应聘难度** :秋招略大于春招; +4. **招聘公司:** 秋招数量多,而春招数量较少,一般为秋招的补充。 + +**综上,一般来说,秋招的含金量明显是高于春招的。** + +**下面我就说一下我自己知道的一些方法,不过应该也涵盖了大部分获取面试机会的方法。** + +1. **关注大厂官网,随时投递简历(走流程的网申);** +2. **线下参加宣讲会,直接投递简历;** +3. **找到师兄师姐/认识的人,帮忙内推(能够让你避开网申简历筛选,笔试筛选,还是挺不错的,不过也还是需要你的简历够棒);** +4. **博客发文被看中/Github优秀开源项目作者,大厂内部人员邀请你面试;** +5. **求职类网站投递简历(不是太推荐,适合海投);** + + +除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。 + +### 2 面试前的准备 + +### 2.1 准备自己的自我介绍 + +从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。 + +我这里简单分享一下我自己的自我介绍的一个简单的模板吧: + +> 面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。 + +### 2.2 关于着装 + +穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 + +### 2.3 随身带上自己的成绩单和简历 + +有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。 + +### 2.4 如果需要笔试就提前刷一些笔试题 + +平时空闲时间多的可以刷一下笔试题目(牛客网上有很多)。但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。 + +### 2.5 花时间一些逻辑题 + +面试中发现有些公司都有逻辑题测试环节,并且都把逻辑笔试成绩作为很重要的一个参考。 + +### 2.6 准备好自己的项目介绍 + +如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑: + +1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图) +2. 在这个项目中你负责了什么、做了什么、担任了什么角色 +3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 +4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 + +### 2.7 提前准备技术面试 + +搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) + +### 2.7 面试之前做好定向复习 + +所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 + +举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 + +# 3 面试之后复盘 + +如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! \ No newline at end of file diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/java programmer need know.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/java programmer need know.md" new file mode 100644 index 00000000000..ef111f4cf15 --- /dev/null +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/java programmer need know.md" @@ -0,0 +1,81 @@ +  身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学那些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指名一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。 + +### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗? + +  我自己也是非985非211学校的,结合自己的经历以及一些朋友的经历,我觉得让我回答这个问题再好不过。 + +  首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。 + +  企业HR肯定是更喜欢高学历的人,毕竟985,211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢? +   +  双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。** + + +### Question2:非计算机专业的学生能学好Java后台吗?我能进大厂吗? + +  当然可以!现在非科班的程序员很多,很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班,我觉得他们很多人学的都还不错。另外,我的一个朋友本科是机械专业,大一开始自学安卓,技术贼溜,在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答,即使你是非科班程序员,如果你想进入大厂的话,你也可以通过自己的其他优势来弥补。 + +  我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。 + +  建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据机构与算法!一定学好数据机构与算法!一定学好数据机构与算法!”,重要的东西说3遍。 + + + +### Question3: 我没有实习经历的话找工作是不是特别艰难? + +  没有实习经历没关系,只要你有拿得出手的项目或者大赛经历的话,你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历,单纯就是凭借自己的项目经验撑起了整个面试。 + +  如果你既没有实习经历,又没有拿得出手的项目或者大赛经历的话,我觉得在简历关,除非你有其他特别的亮点,不然,你应该就会被刷。 + +### Question4: 我该如何准备面试呢?面试的注意事项有哪些呢? + +下面是我总结的一些准备面试的Tips以及面试必备的注意事项: + +1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账); +2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。) +3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。 +4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) +5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 +6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图;②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 +7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。 + + +**一些还算不错的 Java面试/学习相关的仓库,相信对大家准备面试一定有帮助:**[盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd) + +### Question5: 我该自学还是报培训班呢? + +  我本人更加赞同自学(你要知道去了公司可没人手把手教你了,而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见,你学个东西还要去培训班,说明什么,同等水平下,你的自学能力以及自律能力一定是比不上自学的人的)。但是如果,你连每天在寝室坚持学上8个小时以上都坚持不了,或者总是容易半途而废的话,我还是推荐你去培训班。观望身边同学去培训班的,大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。 + +  另外,如果自律能力不行,你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。 + +  总结:去不去培训班主要还是看自己,如果自己能坚持自学就自学,坚持不下来就去培训班。 + +### Question6: 没有项目经历/博客/Github开源项目怎么办? + +  从现在开始做! + +  网上有很多非常不错的项目视频,你就跟着一步一步做,不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。 + +  如果有自己的博客,也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客,当然,你也可以自己搭建一个博客(采用 Hexo+Githu Pages 搭建非常简单)。写一些什么?学习笔记、实战内容、读书笔记等等都可以。 + +  多用 Github,用好 Github,上传自己不错的项目,写好 readme 文档,在其他技术社区做好宣传。相信你也会收获一个不错的开源项目! + + +### Question7: 大厂到底青睐什么样的应届生? + +  从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求,我们大概可以总结归纳出下面这 4 点能给简历增加很多分数: + +- 参加过竞赛(含金量超高的是ACM); +- 对数据结构与算法非常熟练; +- 参与过实际项目(比如学校网站); +- 参与过某个知名的开源项目或者自己的某个开源项目很不错; + +  除了我上面说的这三点,在面试Java工程师的时候,下面几点也提升你的个人竞争力: + +- 熟悉Python、Shell、Perl等脚本语言; +- 熟悉 Java 优化,JVM调优; +- 熟悉 SOA 模式; +- 熟悉自己所用框架的底层知识比如Spring; +- 了解分布式一些常见的理论; +- 具备高并发开发经验;大数据开发经验等等。 + diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" index 36eaa60dff1..4ca58dbfff6 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" @@ -153,6 +153,7 @@ Java程序设计语言对对象采用的不是引用调用,实际上,对象 **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) **equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: + - 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。 - 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 @@ -180,6 +181,7 @@ public class test1 { ``` **说明:** + - String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 - 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。 diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" index 88aa2a6ec4a..426498cb2d9 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" @@ -173,6 +173,8 @@ Java语言通过字节码的方式,在一定程度上解决了传统解释型 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 +注意:Java8 后接口可以有默认实现( default )。 + ### 成员变量与局部变量的区别有那些? 1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰; diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" index c9a6ebf38d5..3cb02d73d5b 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" @@ -20,7 +20,7 @@ 2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。 3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。 4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: - - **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。 + - **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waiting queue)中。 - **(二). 同步阻塞**:运行(running)的线程在获取对象的同步锁时,若该同步 锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 - **(三). 其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。 @@ -82,10 +82,10 @@ public class Run { ![结果](https://user-gold-cdn.xitu.io/2018/3/20/16243e80f22a2d54?w=161&h=54&f=jpeg&s=7380) 从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。 -###②实现Runnable接口 +### ②实现Runnable接口 推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。 -。 +可以通过调用 Thead 类的 `setDaemon(true)` 方法设置当前的线程为守护线程。 注意事项: @@ -178,7 +178,7 @@ Thread类中包含的成员变量代表了线程的某些优先级。如**Thread - 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁** 。 - 两者都可以暂停线程的执行。 - Wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。 -- wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者nofifyAl()方法。sleep()方法执行完成后,线程会自动苏醒。 +- wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒。 ## 9 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" index 91c24d1a180..d07fa52a7e7 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" @@ -1,14 +1,11 @@ -![程序员的简历之道](https://user-gold-cdn.xitu.io/2018/7/25/164cf5c26e88cbcd?w=1024&h=902&f=jpeg&s=94869) +# 程序员的简历就该这样写 -> 俗话说的好:“工欲善其事,必先利其器”。准备一份好的简历对于能不能找到一份好工作起到了至关重要的作用。 +### 1 前言 +一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。 +### 2 为什么说简历很重要? - -## 六 如何写自己的简历? - -一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 - -### 6.1 为什么说简历很重要? +#### 2.1 先从面试前来说 假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。 @@ -16,52 +13,88 @@ 另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。 -### 6.2 这3点你必须知道 +所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。 + +#### 2.2 再从面试中来说 + +我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。 + +所以,首先,你要明确的一点是:**你不会的东西就不要写在简历上**。另外,**你要考虑你该如何才能让你的亮点在简历中凸显出来**,比如:你在某某项目做了什么事情解决了什么问题(只要有项目就一定有要解决的问题)、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。 + +面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。 -1. **大部分应届生找工作的硬伤是没有工作经验或实习经历;** -2. **写在简历上的东西一定要慎重,这可能是面试官大量提问的地方;** -3. **将自己的项目经历完美的展示出来非常重要。** +### 3 下面这几点你必须知道 -### 6.3 两大法则了解一下 -目前写简历的方式有两种普遍被认可,一种是 STAR, 一种是 FAB。 +1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。 +2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作** +3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;** +4. **将自己的项目经历完美的展示出来非常重要。** -**STAR法则(Situation Task Action Result):** +### 4 必须了解的两大法则 + + +**①STAR法则(Situation Task Action Result):** - **Situation:** 事情是在什么情况下发生; - **Task::** 你是如何明确你的任务的; - **Action:** 针对这样的情况分析,你采用了什么行动方式; - **Result:** 结果怎样,在这样的情况下你学习到了什么。 -**FAB 法则(Feature Advantage Benefit):** +简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。 + +下面这段内容摘自百度百科,我觉得写的非常不错: + +> STAR法则,500强面试题回答时的技巧法则,备受面试者成功者和500强HR的推崇。 +由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容) +在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。 +但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗? + +**②FAB 法则(Feature Advantage Benefit):** - **Feature:** 是什么; - **Advantage:** 比别人好在哪些地方; - **Benefit:** 如果雇佣你,招聘方会得到什么好处。 -### 6.4 项目经历怎么写? +简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。 + +### 5 项目经历怎么写? + 简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: 1. 对项目整体设计的一个感受 2. 在这个项目中你负责了什么、做了什么、担任了什么角色 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 -4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。 - -### 6.5 专业技能该怎么写? -先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写: +4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 + +### 6 专业技能该怎么写? +先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善): + +- 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握 +- Java 基础知识:掌握 +- JVM 虚拟机(Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理):掌握 +- 高并发、高可用、高性能系统开发:掌握 +- Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握 +- SSH 整合、SSM 整合、 SOA 架构:掌握 +- Dubbo: 掌握 +- Zookeeper: 掌握 +- 常见消息队列: 掌握 +- Linux:掌握 +- MySQL常见优化手段:掌握 +- Spring Boot +Spring Cloud +Docker:了解 +- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解 +- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉 + +### 7 开源程序员Markdown格式简历模板分享 -- Dubbo:精通 -- Spring:精通 -- Docker:掌握 -- SOA分布式开发 :掌握 -- Spring Cloud:了解 +分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 +Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) -### 6.6 开源程序员简历模板分享 +我的下面这篇文章讲了如何写一份Markdown格式的简历,另外,文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。 -分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 -Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) +[手把手教你用Markdown写一份高质量的简历](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484347&idx=1&sn=a986ea7e199871999a5257bd3ed78be1&chksm=fd9855dacaefdccc2c5d5f8f79c4aa1b608ad5b42936bccaefb99a850a2e6e8e2e910e1b3153&token=719595858&lang=zh_CN#rd) -### 6.7 其他的一些小tips +### 8 其他的一些小tips 1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。 @@ -71,10 +104,3 @@ Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/ 6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 - - -本文摘自我的Gitchat:[《从应届程序员角度分析如何备战大厂面试》](https://gitbook.cn/gitchat/activity/5b457a5df64d4d62e64a449a)。 - - - - diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..422d9402f12 --- /dev/null +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -0,0 +1,950 @@ + + +- [一 基础篇](#一-基础篇) + - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么) + - [2. 说一下转发\(Forward\)和重定向\(Redirect\)的区别](#2-说一下转发forward和重定向redirect的区别) + - [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议) + - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手) + - [为什么要三次握手](#为什么要三次握手) + - [为什么要传回 SYN](#为什么要传回-syn) + - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) + - [为什么要四次挥手](#为什么要四次挥手) + - [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别) + - [6. HTTP请求,响应报文格式](#6-http请求响应报文格式) + - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引) + - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不) + - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式) + - [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗) + - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量) + - [事务传播行为](#事务传播行为) + - [隔离级别](#隔离级别) + - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗) + - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理) +- [二 进阶篇](#二-进阶篇) + - [1 消息队列MQ的套路](#1-消息队列mq的套路) + - [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处) + - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能) + - [2)降低系统耦合性](#2降低系统耦合性) + - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗) + - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢) + - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望) + - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别) + - [2.1 两者的对比](#21-两者的对比) + - [2.2 关于两者的总结](#22-关于两者的总结) + - [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧) + - [3.1 Arraylist 与 LinkedList 有什么不同?\(注意加上从数据结构分析的内容\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容) + - [3.2 HashMap的底层实现](#32-hashmap的底层实现) + - [1)JDK1.8之前](#1jdk18之前) + - [2)JDK1.8之后](#2jdk18之后) + - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解) + - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了) + - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别) +- [三 终结篇](#三-终结篇) + - [1. Object类有哪些方法?](#1-object类有哪些方法) + - [1.1 Object类的常见方法总结](#11-object类的常见方法总结) + - [1.2 hashCode与equals](#12-hashcode与equals) + - [1.2.1 hashCode\(\)介绍](#121-hashcode介绍) + - [1.2.2 为什么要有hashCode](#122-为什么要有hashcode) + - [1.2.3 hashCode\(\)与equals\(\)的相关规定](#123-hashcode与equals的相关规定) + - [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的) + - [1.3 ==与equals](#13-与equals) + - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题) + - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别) + - [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现) + - [JDK1.7\(上面有示意图\)](#jdk17上面有示意图) + - [JDK1.8\(上面有示意图\)](#jdk18上面有示意图) + - [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别) + - [4 线程池了解吗?](#4-线程池了解吗) + - [4.1 为什么要用线程池?](#41-为什么要用线程池) + - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么) + - [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池) + - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍) + - [4.3 创建的线程池的方式](#43-创建的线程池的方式) + - [5 Nginx](#5-nginx) + - [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx) + - [反向代理](#反向代理) + - [负载均衡](#负载均衡) + - [动静分离](#动静分离) + - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx) + - [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗) + + + + +这些问题是2018年去美团面试的同学被问到的一些常见的问题,希望对你有帮助! + +# 一 基础篇 + + +## 1. `System.out.println(3|9)`输出什么? + +正确答案:11. + +**考察知识点:&和&&;|和||** + +**&和&&:** + +共同点:两者都可做逻辑运算符。它们都表示运算符的两边都是true时,结果为true; + +不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为true,是true就继续运算右边的然后判断并输出,是false就停下来直接输出不会再运行后面的东西。 + +**|和||:** + +共同点:两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true,结果为true,两边都不是true,结果就为false; + +不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为true,是true就停下来直接输出不会再运行后面的东西,是false就继续运算右边的然后判断并输出。 + +**回到本题:** + +3 | 9=0011(二进制) | 1001(二进制)=1011(二进制)=11(十进制) + +## 2. 说一下转发(Forward)和重定向(Redirect)的区别 + +**转发是服务器行为,重定向是客户端行为。** + +**转发(Forword)** 通过RequestDispatcher对象的`forward(HttpServletRequest request,HttpServletResponse response)`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。 + +```java +request.getRequestDispatcher("login_success.jsp").forward(request, response); +``` + +**重定向(Redirect)** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。 + +1. **从地址栏显示来说:** forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL. +2. **从数据共享来说:** forward:转发页面和转发到的页面可以共享request里面的数据. redirect:不能共享数据. +3. **从运用地方来说:** forward:一般用于用户登陆的时候,根据角色转发到相应的模块. redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等 +4. **从效率来说:** forward:高. redirect:低. + + +## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议 + +图片来源:《图解HTTP》: + +![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) + +总体来说分为以下几个过程: + +1. DNS解析 +2. TCP连接 +3. 发送HTTP请求 +4. 服务器处理请求并返回HTTP报文 +5. 浏览器解析渲染页面 +6. 连接结束 + +具体可以参考下面这篇文章: + +- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700) + +## 4. TCP 三次握手和四次挥手 + +为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。 + +**漫画图解:** + +图片来源:《图解HTTP》 +![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095) + +**简单示意图:** +![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088) + +- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端 +- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端 +- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端 + +#### 为什么要三次握手 + +**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。** + +第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常。 + +第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常 + +第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常 + +所以三次握手就能确认双发收发功能都正常,缺一不可。 + +#### 为什么要传回 SYN +接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。 + +> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。 + + +#### 传了 SYN,为啥还要传 ACK + +双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。 + +![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406) + +断开一个 TCP 连接则需要“四次挥手”: + +- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送 +- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号 +- 服务器-关闭与客户端的连接,发送一个FIN给客户端 +- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1 + + +#### 为什么要四次挥手 + +任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。 + +举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 + +上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891) + + + +## 5. IP地址与MAC地址的区别 + +参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597) + +IP地址是指互联网协议地址(Internet Protocol Address)IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。 + + + +MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的MAC地址。 + +## 6. HTTP请求,响应报文格式 + + + +HTTP请求报文主要由请求行、请求头部、请求正文3部分组成 + +HTTP响应报文主要由状态行、响应头部、响应正文3部分组成 + +详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273) + +## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引? + +**为什么要使用索引?** + +1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 +2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。 +3. 帮助服务器避免排序和临时表 +4. 将随机IO变为顺序IO +5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 + +**索引这么多优点,为什么不对表中的每一个列创建一个索引呢?** + +1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 +2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 +3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 + +**索引是如何提高查询速度的?** + +将无序的数据变成相对有序的数据(就像查目录一样) + +**说一下使用索引的注意事项** + +1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。 +2. 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 +3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描 +4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 chema_unused_indexes 视图来查询哪些索引从未被使用 +5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能 + +**Mysql索引主要使用的哪两种数据结构?** + +- 哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。 +- BTree索引:Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。 + +更多关于索引的内容可以查看我的这篇文章:[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd) + +**什么是覆盖索引?** + +如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称 +之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! + + +## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不? + **进程与线程的区别是什么?** + +线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 + +**进程间的几种通信方式说一下?** + + +1. **管道(pipe)**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe(无名管道)和fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。 +2. **信号量(semophore)**:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 +3. **消息队列(message queue)**:消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。 +4. **信号(signal)**:信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。 +5. **共享内存(shared memory)**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 +6. **套接字(socket)**:socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。 + +**线程间的几种通信方式知道不?** + +1、锁机制 + +- 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。 +- 读写锁:允许多个线程同时读共享数据,而对写操作互斥。 +- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。 + +2、信号量机制:包括无名线程信号量与有名线程信号量 + +3、信号机制:类似于进程间的信号处理。 + +线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。 + +## 9. 为什么要用单例模式?手写几种线程安全的单例模式? + +**简单来说使用单例模式可以带来下面几个好处:** + +- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销; +- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。 + +**懒汉式(双重检查加锁版本)** + +```java +public class Singleton { + + //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量 + private volatile static Singleton uniqueInstance; + private Singleton() { + } + public static Singleton getInstance() { + //检查实例,如果不存在,就进入同步代码块 + if (uniqueInstance == null) { + //只有第一次才彻底执行这里的代码 + synchronized(Singleton.class) { + //进入同步代码块后,再检查一次,如果仍是null,才创建实例 + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } +} +``` + +**静态内部类方式** + +静态内部实现的单例是懒加载的且线程安全。 + +只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。 + +```java +public class Singleton { + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + private Singleton (){} + public static final Singleton getInstance() { + return SingletonHolder.INSTANCE; + } +} +``` + +## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗? + +在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。 + +Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。 + +![pring的bean的作用域](https://user-gold-cdn.xitu.io/2018/11/10/166fd45773d5dd2e?w=563&h=299&f=webp&s=27930) + +Spring的bean的生命周期以及更多内容可以查看:[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd) + + +## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量? + +#### 事务传播行为 + +事务传播行为(为了解决业务层方法之间互相调用的事务问题): +当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量: + +**支持当前事务的情况:** + +- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 +- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 +- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性) + +**不支持当前事务的情况:** + +- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 +- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 +- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 + +**其他情况:** + +- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。 + + +#### 隔离级别 + +TransactionDefinition 接口中定义了五个表示隔离级别的常量: + +- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别. +- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 +- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 +- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 +- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 + +## 12. SpringMVC 原理了解吗? + +![SpringMVC 原理](https://user-gold-cdn.xitu.io/2018/11/10/166fd45787394192?w=1015&h=466&f=webp&s=35352) + +客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Model)->将得到视图对象返回给用户 + +关于 SpringMVC 原理更多内容可以查看我的这篇文章:[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd) + +## 13. Spring AOP IOC 实现原理 + +过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。 + +**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。 + +**AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。 + + + +# 二 进阶篇 + +## 1 消息队列MQ的套路 + +消息队列/消息中间件应该是Java程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可! + +### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处 + +面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。 + +**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。** + +#### 1)通过异步处理提高系统性能 +![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123) +如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。** + +通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: +![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550) +因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。 + +#### 2)降低系统耦合性 +我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和2.**分布式服务**。 + +> **先来简单说一下分布式服务:** + +目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c) + +> **再来谈我们的分布式消息队列:** + +我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 + +我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: +![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946) +**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。 + +消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 + +**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。** + +**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。 + +> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题! + +### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗? + +- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了! +- **系统复杂性提高:** 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题! +- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了! + +> 了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可! + +### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢? + + +| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafaka | +| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | +| 单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 | +| topic数量对吞吐量的影响 | | | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 | +| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | +| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 | +| 时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 | +| 功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 | +| 优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 | + +> 这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可! + +### 1.4 关于消息队列其他一些常见的问题展望 + +1. 引入消息队列之后如何保证高可用性 +2. 如何保证消息不被重复消费呢? +3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)? +4. 我该怎么保证从消息队列里拿到的数据按顺序执行? +5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? +6. 如果让你来开发一个消息队列中间件,你会怎么设计架构? + + + +## 2 谈谈 InnoDB 和 MyIsam 两者的区别 + +### 2.1 两者的对比 + +1. **count运算上的区别:** 因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存 +2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 +3. **是否支持外键:** MyISAM不支持,而InnoDB支持。 + + +### 2.2 关于两者的总结 + +MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。 + +一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。 + + +## 3 聊聊 Java 中的集合吧! + +### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容) + +- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; +- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:); +- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** +- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 +- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 + +**补充内容:RandomAccess接口** + +```java +public interface RandomAccess { +} +``` + +查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 + +在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 + +```java + public static + int binarySearch(List> list, T key) { + if (list instanceof RandomAccess || list.size() Java 中的集合这类问题几乎是面试必问的,问到这类问题的时候,HashMap 又是几乎必问的问题,所以大家一定要引起重视! + +### 3.2 HashMap的底层实现 + +#### 1)JDK1.8之前 + +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** + +**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** + +**JDK 1.8 HashMap 的 hash 方法源码:** + +JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 + +```java + static final int hash(Object key) { + int h; + // key.hashCode():返回散列值也就是hashcode + // ^ :按位异或 + // >>>:无符号右移,忽略符号位,空位都以0补齐 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } +``` +对比一下 JDK1.7的 HashMap 的 hash 方法源码. + +```java +static int hash(int h) { + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); +} +``` + +相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。 + +所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 + + + +![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991) + + +#### 2)JDK1.8之后 + +相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 + +![JDK1.8之后的HashMap底层数据结构](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c351da9?w=720&h=545&f=jpeg&s=23933) + +TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 + +> 问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题! + +### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解 + +![红黑树](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c138cba?w=851&h=614&f=jpeg&s=34458) + +**红黑树特点:** + +1. 每个节点非红即黑; +2. 根节点总是黑色的; +3. 每个叶子节点都是黑色的空节点(NIL节点); +4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); +5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) + + +**红黑树的应用:** + +TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。 + +**为什么要用红黑树** + +简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 + + +### 3.4 红黑树这么优秀,为何不直接使用红黑树得了? + +说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。 + +### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别 + +**HashMap 和 Hashtable 的区别** + +1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); +2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; +3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。 +4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 +5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 + +**HashSet 和 HashMap 区别** + +如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。) + +![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536) + +# 三 终结篇 + +## 1. Object类有哪些方法? + +这个问题,面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言,大家都要掌握! + +### 1.1 Object类的常见方法总结 + +Object类是一个特殊的类,是所有类的父类。它主要提供了以下11个方法: + +```java + +public final native Class getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。 + +public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。 +public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。 + +protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。 + +public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。 + +public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。 + +public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。 + +public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。 + +public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。 + +public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念 + +protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作 + +``` + +> 问完上面这个问题之后,面试官很可能紧接着就会问你“hashCode与equals”相关的问题。 + +### 1.2 hashCode与equals + +面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” + +#### 1.2.1 hashCode()介绍 + +hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。 + +```java + public native int hashCode(); +``` + +散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) + +#### 1.2.2 为什么要有hashCode + + +**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:** + +当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。 + + +#### 1.2.3 hashCode()与equals()的相关规定 + +1. 如果两个对象相等,则hashcode一定也是相同的 +2. 两个对象相等,对两个对象分别调用equals方法都返回true +3. 两个对象有相同的hashcode值,它们也不一定是相等的 +4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖** +5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) + +#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的? + +在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。 + +因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。 + +我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。 + +> ==与equals 的对比也是比较常问的基础问题之一! + +### 1.3 ==与equals + +**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) + +**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: + +- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。 +- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 + + +**举个例子:** + +```java +public class test1 { + public static void main(String[] args) { + String a = new String("ab"); // a 为一个引用 + String b = new String("ab"); // b为另一个引用,对象的内容一样 + String aa = "ab"; // 放在常量池中 + String bb = "ab"; // 从常量池中查找 + if (aa == bb) // true + System.out.println("aa==bb"); + if (a == b) // false,非同一对象 + System.out.println("a==b"); + if (a.equals(b)) // true + System.out.println("aEQb"); + if (42 == 42.0) { // true + System.out.println("true"); + } + } +} +``` + +**说明:** + +- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 +- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。 + +> 在[【备战春招/秋招系列5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题:HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀,为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视! + +## 2 ConcurrentHashMap 相关问题 + +### 2.1 ConcurrentHashMap 和 Hashtable 的区别 + +ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 + +- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; +- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 + +**两者的对比图:** + +图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html + +HashTable: +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg) + +JDK1.7的ConcurrentHashMap: +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg) +JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 +Node: 链表节点): +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg) + +### 2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现 + +#### JDK1.7(上面有示意图) + +首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 + +**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 + +Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 + +```java +static class Segment extends ReentrantLock implements Serializable { +} +``` + +一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 + +#### JDK1.8(上面有示意图) + +ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。 + +synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 + +## 3 谈谈 synchronized 和 ReenTrantLock 的区别 + +**① 两者都是可重入锁** + +两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 + +**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** + +synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 + +**③ ReenTrantLock 比 synchronized 增加了一些高级功能** + +相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** + +- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 +- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 +- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 + +如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 + +**④ 两者的性能已经相差无几** + +在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。 + + +## 4 线程池了解吗? + + +### 4.1 为什么要用线程池? + +线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 + +这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处: + +- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 +- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 +- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 + +### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么? + +#### Java 主要提供了下面4种线程池 + +- **FixedThreadPool:** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 +- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 +- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 +- **ScheduledThreadPoolExecutor:** 主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor又分为:ScheduledThreadPoolExecutor(包含多个线程)和SingleThreadScheduledExecutor (只包含一个线程)两种。 + +#### 各种线程池的适用场景介绍 + +- **FixedThreadPool:** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器; +- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。 +- **CachedThreadPool:** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器; +- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景, +- **SingleThreadScheduledExecutor:** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。 + +### 4.3 创建的线程池的方式 + +**(1) 使用 Executors 创建** + +我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池,另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 + +```java +Executors 返回线程池对象的弊端如下: + +FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 +CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 + +``` +**(2) ThreadPoolExecutor的构造函数创建** + + +我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下: + +```java +private static ExecutorService executor = new ThreadPoolExecutor(13, 13, + 60L, TimeUnit.SECONDS, + new ArrayBlockingQueue(13)); +``` + +这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。 + +**(3) 使用开源类库** + +Hollis 大佬之前在他的文章中也提到了:“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例: + +```java +public class ExecutorsDemo { + + private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() + .setNameFormat("demo-pool-%d").build(); + + private static ExecutorService pool = new ThreadPoolExecutor(5, 200, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); + + public static void main(String[] args) { + + for (int i = 0; i < Integer.MAX_VALUE; i++) { + pool.execute(new SubThread()); + } + } +} +``` + +通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。 + +## 5 Nginx + +### 5.1 简单介绍一下Nginx + +Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。 + +#### 反向代理 + +谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了 + +- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN了。 +- **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。 + +通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/): + +![正向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/60925795.jpg) + +![反向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/62563930.jpg) + +所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。 + +#### 负载均衡 + +在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。 + +Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。 + +负载均衡相比于反向代理更侧重的时将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。 + +#### 动静分离 + +动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。 + +### 5.2 为什么要用 Nginx? + +> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。 + +如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。 + +Nginx 有以下5个优点: + +1. 高并发、高性能(这是其他web服务器不具有的) +2. 可扩展性好(模块化设计,第三方插件生态圈丰富) +3. 高可靠性(可以在服务器行持续不间断的运行数年) +4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx服务的情况下升级 Nginx) +5. BSD许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本) + +### 5.3 Nginx 的四个主要组成部分了解吗? + +> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。 + +- Nginx 二进制可执行文件:由各模块源码编译出一个文件 +- Nginx.conf 配置文件:控制Nginx 行为 +- acess.log 访问日志: 记录每一条HTTP请求信息 +- error.log 错误日志:定位问题