Java容器(一)

Java容器(一)

说说List,Set,Map三者的区别?

  • List 有序
  • Set 唯一
  • Map 使用键值对存储。

Arraylist 与 LinkedList 区别?

  1. 是否保证线程安全

ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  1. 底层数据结构

Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构 (JDK1.6还是双向循环链表)

  1. 插入和删除是否受元素位置的影响

Arraylist插入和删除受元素所在的位置的影响。

① 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)。

  1. 是否支持快速随机访问

基于数组结构的ArrayList是可以的。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

LinkedList 不支持高效的随机元素访问。

  1. 内存空间占用

ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

快速随机访问接口:RandomAccess

1
2
public interface RandomAccess {
}

查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。

RandomAccess 接口只是标识,并不是说 ArrayList 实现 RandomAccess 接口才具有快速随机访问功能的!

List遍历

1
2
实现了 RandomAccess 接口的list,优先选择普通 for 循环 ,其次 foreach,
未实现 RandomAccess 接口的list,优先选择iterator遍历(foreach遍历底层也是通过iterator实现的,),大size的数据,千万不要使用普通for循环

ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢?

Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时建议使用Arraylist。

HashMap 的三个问题

HashMap 的三个问题

##问题1:HashM安排的初始长度,为什么?

初始长度是 16,每次扩展或者是手动初始化,长度必须是 2的幂。

因为: index = HashCode(Key) & (length - 1), 如果 length是 2的 幂的话,则 length - 1就是 全是 1的二进制数,比如  16 - 1 = 1111,这样相当于是 坐落在长度为 length的hashMap上的位置只和 HashCode的后四位有关,这只要给出的HashCode算法本身分布均匀,算出的index就是分布均匀的。

  因为HashMap的key是int类型,所以最大值是2^31次方,但是查看源码,当到达 2^30次方,即

MAXIMUM_CAPACITY,之后,便不再进行扩容。

问题2:高并发情况下,为什么HashMap出现死锁?

我们看到默认HashMap的初始长度是16,比较小,每一次push的时候,都会检查当前容量是否超过 预定的 threshold,如果超过,扩大HashMap容量一倍,整个表里的所有元素都需要按照新的hash算法被算一遍,这个代价较大。提到死锁,对于 HashMap来说,貌似只能和链表操作有关。

正常ReHash过程,可以看到,每个元素重新算hash值,将链表翻转(目的遍历每个bucket上的链表还是用的是头插法,时间复杂度最低),放到对应的bucket上的链表中

问题3:java8对hashMap做了什么优化?

简单说: java7中 hashMap每个桶中放置的是链表,这样当hash碰撞严重时,会导致个别位置链表长度过长,从而影响性能。

java8中,HashMap 每个桶中当链表长度超过8之后,会将链表转换成红黑树,从而提升增删改查的速度。

JVM相关问题

JVM相关问题

1.JVM内存模型,GC机制和原理。

2.GC分两种,Minor GC和 Full GC有什么区别?什么时候会触发Full GC?分别采用什么算法?

3.JVM里有几种classloader,为什么会有多种?

4.什么是双亲委派机制?介绍一些运作过程,双亲委派模式的好处?

5.什么情况下我们需要破坏双亲委派模型?

6.常见的JVM调优有哪些?可以具体到那个参数,调成什么值?

7.JVM虚拟机内存划分、类加载器、垃圾收集算法、垃圾收集器、class文件结构是如何解析的?

BIO,NIO,AIO 总结

BIO,NIO,AIO IO模型总结

Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。
程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。
只需要使用Java的API就可以了。

同步与异步

同步:

同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。

异步:

异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,
被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。

阻塞和非阻塞

阻塞:

阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。

非阻塞:

非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

1
2
3
4
5
举个生活中简单的例子,你妈妈让你烧水,

小时候你比较笨啊,在那里傻等着水开(同步阻塞)。
等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。
后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。

BIO (Blocking I/O)

同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。

传统 BIO

BIO通信(一请求一应答)

采用 BIO 通信模型 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。
我们一般通过在while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求,
请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,
只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接

如果要让 BIO 通信模型能够同时处理多个客户端请求,就必须使用多线程
(主要原因是socket.accept()、socket.read()、socket.write() 涉及的三个主要函数都是同步阻塞的),
也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,
通过输出流返回应答给客户端,线程销毁。这就是典型的 一请求一应答通信模型 。
我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过线程池机制改善,
线程池还可以让线程的创建和回收成本相对较低。使用FixedThreadPool 可以有效的控制了线程的最大数量,
保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M)

我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?

1
2
3
4
Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。
尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。
如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,
不能对外提供服务。

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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();

}

}



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();

}

}

伪异步 IO

为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一
后端通过一个线程池来处理多个客户端的请求接入,
形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.
通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。

采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架

当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中
进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小
和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。
不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。

总结

在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O
并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的
连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O
处理模型来应对更高的并发量。

NIO (New I/O)

NIO是一种同步非阻塞的I/O模型,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。

NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。
NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道
实现,两种通道都支持阻塞和非阻塞两种模式。

阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;
非阻塞模式正好与之相反。

对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;
对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

区别:(关键)

1) Non-blocking IO(非阻塞IO)
Java IO流:
阻塞的。这意味着,当一个线程调用 read() 或 write() 时,该线程被阻塞,
直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

NIO流:
不阻塞的。Java NIO使我们可以进行非阻塞IO操作。
比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,
线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,
但不需要等待它完全写入,这个线程同时可以去做别的事情。

2)Buffer(缓冲区)
IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。
Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。
Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels;
Buffer本质上就是一块内存区;

一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。

IO:
在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。
虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,
而 NIO 却是直接读到 Buffer 中进行操作。

NIO流:
在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;
在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。
除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。

3)Channel (通道)
NIO 通过Channel(通道) 进行读写。

通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。

4)Selector (选择器)
NIO有选择器,而IO没有。

选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。
因此,为了提高系统效率选择器是有用的。

NIO 包含下面几个核心的组件:

Channel (通道)
Buffer (缓冲区)
Selector(选择器)

AIO (Asynchronous I/O)

AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,
操作系统会通知相应的线程进行后续的操作。

AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。
对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。

Spring Boot 介绍

Spring Boot 介绍

Spring Boot 提供了一组工具只需要极少的配置就可以快速的构建并启动基于 Spring 的应用程序。解决了传统 Spring 开发需要配置大量配置文件的痛点,同时 Spring Boot 对于第三方库设置了合理的默认值,可以快速的构建起应用程序。当然 Spring Boot 也可以轻松的自定义各种配置,无论是在开发的初始阶段还是投入生成的后期阶段。

Spring Boot 优点

1.快速的创建可以独立运行的 Spring 项目以及与主流框架的集成。

2.使用嵌入式的 Servlet 容器,用于不需要打成war包。

3.使用很多的启动器(Starters)自动依赖与版本控制。

4.大量的自动化配置,简化了开发,当然,我们也可以修改默认值。

5.不需要配置 XML 文件,无代码生成,开箱即用。

6.准生产环境的运行时应用监控。

7.与云计算的天然集成。

Spring源码

#Spring源码

spring-jcl日志源码分析

1.spring的基本应用和spring源码的编译

2.java混乱的日志系统,JUL,JCL,log4j,slf4j

spring aop源码分析

1.AspectJ和spring AOP, aspectj的静态织入

2.JDK动态代理的源码分析,JDK如何操作字节码

3.spring通过cglib完成AOP,cglib如何完成方法拦截

4.AnnotationAwareAspectJAutoProxyCreator是如何实现代理织入的

spring IOC、AOP、MVC源码分析

1.BeanDefinition作用,如何改变bean的行为

2.BeanDefinitionRegistry的作用,源码分析

3.BeanNameGenerator如何改变beanName的生成策略

4.BeanPostProcessor在bean实例化过程中可以做什么?经典应用场景有哪些?

JVM基础

JVM,什么是JVM?

Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

Java虚拟机在软件层层面屏蔽了底层硬件、底层指令的细节。
跨平台:程序可以运行基于不同平台版本的jvm就可以了。

数据类型
Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

基本类型包括:byte,short,int,long,char,float,double,Boolean

引用类型包括:类类型,接口类型和数组。

App.java 源码

编译时环境(jdk)

App.class 字节码

JVM
Java虚拟机:在软件层层面屏蔽了底层硬件、底层指令的细节。 运行时环境 (jre)

操作系统可以运行的文件 机器码

类加载器 ClassLoader JVM将class文件加载至内存模块。
运行时数据区 Runtime Data Area

线程共享区:  Heap堆, Method Area方法区
线程独占区:  程序计数器,虚拟机栈,本地方法栈

程序最小单元:线程

为什么 Java 中只有值传递?

为什么 Java 中只有值传递?

首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
按值调用(call by value)表示方法接收的是调用者提供的值。
按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。
一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。

Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。

下面再总结一下Java中方法参数的使用情况:

一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
一个方法可以改变一个对象参数的状态。
一个方法不能让对象参数引用一个新的对象。

tomcat源码解析

tomcat源码解析

1.tomcat的总体概述和tomcat的启动流程源码分析

2.tomcat当中的web请求源码分析?一个http请求是如何请求到tomcat的?tomcat如何处理的?

3.tomcat的协议分析,从源码分析tomcat当中各种协议详细配置的意义。

4.tomcat和apache、nginx等主流静态资源服务器的搭配使用

5.tomcat的性能调优?生产环境下如何让tomcat容器的性能达到最高

Thread实现多线程三

Thread实现多线程三

接上文,关系到线程运行状态的几个方法:

6)interrupt方法

interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。

下面看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.IOException;

public class ThreadInterupt {
public static void main(String[] args) throws IOException {
ThreadInterupt test = new ThreadInterupt();
MyThread thread = test.new MyThread();
//线程开始
System.out.println("进入线程" + Thread.currentThread().getName());
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {

}
thread.interrupt();
}

class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("线程" + Thread.currentThread().getName() + "进入睡眠状态");
Thread.currentThread().sleep(10000);
System.out.println("线程" + Thread.currentThread().getName() + "睡眠完毕");
} catch (InterruptedException e) {
System.out.println("线程" + Thread.currentThread().getName() + "得到中断异常");
}
System.out.println("线程" + Thread.currentThread().getName() + "run方法执行完毕");
}
}
}

执行结果 :

1
2
3
4
进入线程main
线程Thread-0进入睡眠状态
线程Thread-0得到中断异常
线程Thread-0run方法执行完毕

从这里可以看出,在主线程中,通过interrupt方法可以中断处于阻塞状态的线程。

那么能不能中断处于非阻塞状态的线程呢?看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.IOException;

public class InteruptRunningThread {

public static void main(String[] args) throws IOException {
InteruptRunningThread test = new InteruptRunningThread();
//线程开始
System.out.println("进入线程" + Thread.currentThread().getName());

MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {

}
thread.interrupt();
}

class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(i < Integer.MAX_VALUE){
System.out.println("while循环, i = " + i + "\r\n");
i++;
}
}
}
}

执行结果:

1
2
3
4
5
6
while循环, i = 394572

while循环, i = 394573

while循环, i = 394574
...//还在继续

运行该程序会发现,while循环会一直运行直到变量i的值超出Integer.MAX_VALUE。所以说直接调用interrupt方法不能中断正在运行中的线程。

但是如果配合isInterrupted()能够中断正在运行的线程,因为调用interrupt方法相当于
将中断标志位置为true,那么可以通过调用isInterrupted()判断中断标志是否被置位来中断线程的执行。比如下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.IOException;

public class InteruptRunningThread {

public static void main(String[] args) throws IOException {
InteruptRunningThread test = new InteruptRunningThread();
//线程开始
System.out.println("进入线程" + Thread.currentThread().getName());

MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {

}
thread.interrupt();
System.out.println("结束线程" + Thread.currentThread().getName());
}

class MyThread extends Thread{
@Override
public void run() {
int i = 0;
/*while(i < Integer.MAX_VALUE){
System.out.println("while循环, i = " + i + "\r\n");
i++;
}*/
while(!isInterrupted() && i<Integer.MAX_VALUE){
System.out.println(i+" while循环");
i++;
}
}
}
}

执行结果:

1
2
3
4
138537 while循环
138538 while循环
138539 while循环
结束线程main

但是一般情况下不建议通过这种方式来中断线程,一般会在MyThread类中增加一个属性 isStop来标志是否结束while循环,然后再在while循环中判断isStop的值。
那么就可以在外面通过调用setStop方法来终止while循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyThread extends Thread{
private volatile boolean isStop = false;

@Override
public void run() {
int i = 0;
while(!isStop && i < Integer.MAX_VALUE){
System.out.println("while循环, i = " + i + "\r\n");
i++;
}
}

public void setStop(boolean stop){
this.isStop = stop;
}
}

7)stop方法

stop方法已经是一个废弃的方法,它是一个不安全的方法。因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。

8)destroy方法

destroy方法也是废弃的方法。基本不会被使用到。