一.基础知识篇
1.1 Java基础知识篇
final, finally, finalize 的区别
final修饰符(关键字)
:被final修饰的类,就意味着不能再派生出新的子类,不能为父类而被子类继承。因此一个类不能即被abstract声明,又被final申明。将变量或方法声明为final,可以保证他们在使用过程中不被修改,被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取,被final声明的方法也同样只能使用,不能重载;finally
是在异常处理的时候提供finally块来执行任何清除操作,不管有没有异常被抛出,捕获,finally块都会被执行。try块中的内容是在无异常的执行到结束,catch中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,放在finally块中执行;finalize()
是方法名,Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,这个方法是有垃圾收集器在确定这个对象没有被引用时对这个对象调用的。他是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器之前对这个对象调用的
java异常处理 Exception、error、运行时异常和一般异常有何异同
image.png
- 可以看出,所有的异常都是由Throwable类,下一层分解为两个分支:Error和Exceprion。
Error层次结构描述了java运行时系统的内部错误和资源耗尽错误。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。应用程序不应该抛出这种类型的对象。
Exceprion这个层次结构又分解为连个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有没有问题,但由于像I/O错误这类异常导致的异常属于其他异常。
常见的RuntimeException(运行时异常):
IndexOutOfBoundsException(下标越界异常)
NullPointerException(空指针异常)
NumberFormatException (String转换为指定的数字类型异常)
ArithmeticException -(算术运算异常 如除数为0)
ArrayStoreException - (向数组中存放与声明类型不兼容对象异常)
SecurityException -(安全异常)
IOException(其他异常)
FileNotFoundException(文件未找到异常。)
IOException(操作输入流和输出流时可能出现的异常。)
EOFException (文件已结束异常)
unchecked exception(非检查异常):包括运行时异常(RuntimeException)和派生于Error类的异常。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。
checked exception(检查异常,编译异常,必须要处理的异常)
也:称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。
说说反射的用途及实现
反射机制是Java语言中一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许对其内部成员进行操作。程序中一般的对象类型都是在编译期就确定下来的,而Java 反射机制可以动态的创建对象并调用其属性,这样对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象即使这个对象在编译期是未知的,反射的核心:是 JVM 在运行时 才动态加载的类或调用方法或属性,他不需要事先(写代码的时候或编译期)知道运行对象是谁,反射提供的功能:
①、在运行时判断任意一个对象所属的类
②、在运行时构造任意一个类的对象
③、在运行时判断任意一个类所具有的成员变量和方法(通过反射设置可以调用 private)
④、在运行时调用人一个对象的方法
类加载的过程:
遇到一个新类时,首先会到方法区查找class文件,如果没有找到就到硬盘中去找class文件,找到后会返回,将class文件加载到方法区中,在类加载的时候,静态成员变量会被分配到方法区的静态区域,非静态成员变量会被分配到非静态区域,然后开始给静态成员变量初始化,赋默认值,赋值完成后,会根据静态成员变量书写的位置赋显示值,然后执行静态代码,当所有的静态代码执行完,类加载才算完成;
父类与子类之间的调用顺序:
a)父类静态代码块;
b)子类静态代码块;
c)父类语句块;
d)父类构造函数;
e)子类语句块;
f)子类构造函数;
g)重写父类的方法,则打印重写后的方法;
public class ClassA {
public ClassA(){
System.out.println("父类构造函数");
}
{
System.out.println("父类语句块");
}
static{
System.out.println("父类静态语句块");
}
public void AMethod(){
System.out.println("父类普通方法");
}
}
public class ClassB extends ClassA{
public ClassB(){
System.out.println("子类构造函数");
}
{
System.out.println("子类语句块");
}
static{
System.out.println("子类静态语句块");
}
@Override
public void AMethod(){
System.out.println("子类普通方法");
}
public static void main(String[] args) {
ClassB b = new ClassB();
b.AMethod();
}
}
执行结果:
父类静态语句块
子类静态语句块
父类语句块
父类构造函数
子类语句块
子类构造函数
子类普通方法
内部类和外部类的调用:
a)内部类可以直接调用外部类的包括private的成员变量,使用外部类引用的this关键字即可
b)外部类调用内部类需要建立内部类对象;
JAVA 中堆和栈的区别,说下java 的内存机制
1.基本数据类型的变量和对象的引用都是在栈中分配的;
2.堆内存用来存放由new创建出来的对象和数组;
3.类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配的内存,堆中的内存地址存放在栈中;
4.实例变量:当使用Java关键字new的时候,系统会在堆中开辟并不一定是连续的空间分配给变量
,是零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的“物理位置”;实例变量的生命周期
:当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收名单中,但并不是马上释放堆中内存;
5.局部变量:由申明在某方法,或某代码(比如for循环)中,执行到他的时候在栈中开辟内存,当局部变量一旦脱离作用域,内存立即释放;
Java的垃圾回收机制:
- 标记回收法:遍历对象图并且记录可达到的对象,以便删除不可到达的对象,一般使用单线程工作并且可能产生内存碎片;
- 标记-压缩回收法:前期与第一种方法相同,只是多了一步,将所有存活对象压缩到内存的一端,这样内存碎片就可以合成一大块可再利用的内存区域,提高了内存利用率;
- 复制回收法:把现有的内存空间分成两部分,gc运行时,他把可到达对象复制到另一半空间,再清空正在使用的空间的全部对象;这种方法适合于短生存期的对象,持续复制长生存期的对象则导致效率降低;
- 分代回收法:把内存空间氛围两个或者多个域,如年轻代和年老代,年轻代的特点是对象会很快被回收,因此在年轻代使用效率比较高的算法,当一个对象经过几次回收后依然存活,对象就会被放入老年的内存空间,老年代则采用标记-压缩算法;
- 引用计数(最简单古老的方法):指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程
- 对象引用遍历(现在大多数 jvm 使用的方法):对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集
- 什么是垃圾回收机:释放那些不再持有引用的对象的内存
Hashcode的作用,与 equal 有什么区别
同样用于鉴定2个对象是否相等的,java集合中有 list 和 set 两类,其中 set不允许元素重复实现,那个这个不允许重复实现的方法,如果用 equal 去比较的话,如果存在1000个元素,
你 new 一个新的元素出来,需要去调用1000次 equal 去逐个和他们比较是否是同一个对象
,这样会大大降低效率。hashcode实际上是返回对象的存储地址
,如果这个位置上没有元素,就把元素直接存储在上面,如果这个位置上已经存在元素,这个时候才去调用equal方法与新元素进行比较
,相同的话就不存了,散列到其他地址上
参考:https://www.jianshu.com/p/5a7f5f786b75
1.2 java-集合篇
Java集合类框架的基本接口有哪些
- Collection集合接口,List、set实现Collection接口
- arraylist、linkedlist,vector实现list接口
- stack继承vector,Map接口,
- hashtable、hashmap实现map接口
Arraylist与linkedlist的区别
都是实现List接口的列表,arrayList是基于数组的数据结构,LinkedList是基于链表的数据结构,当获取特定元素时,arrayList效率比较快,他通过数组下标即可获取,而linkedList需要移动指针,当存储数据与删除数据时,linkedlist效率较快,只需要将指针移动到指定位置增加或删除即可,而arraylist需要移动数据;
String,StringBuffer,StringBuilder的区别:
a)String类型和StringBuffer类型的主要性能区别其实在于String是不可变的对象;
b) StringBuffer和StringBuilder底层是char[]数组实现的
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
c)StringBuffer是线程安全的,StringBulider是线程不安全的;
StringBuffer支持并发操作,线程安全的,适合多线程中使用;
StringBuilder不支持并发操作,线程不安全的,不适合多线程使用,但是在单线程中的性能比StringBufferg高;
d)三者在执行速度方面的比较:StringBuilder > StringBuffer > String
1.3 JVM
JVM运行时内存区域划分
1.4 设计模式
动态代理:
代理模式:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法、实际执行的是被代理类的方法。
image.png
而AOP,是通过动态代理实现的。
一、简单来说:
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)
二、Spring在选择用JDK还是CGLiB的依据:
(1)当Bean实现接口时,Spring就会用JDK的动态代理
(2)当Bean没有实现接口时,Spring使用CGlib是实现
(3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
三、CGlib比JDK快?
(1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
(2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
JDK常用设计模式:
单例模式(Singleton parrern):用于Runtimes,Calendar和其他的一些类中;
工厂模式(Factory pattern): 被用于各种不可变的类,如 Boolean;
观察者模式(Observer pattern): 被用于swing和很多的监听事件中;
装饰器模式(Decorator design pattern):被用于多个Java IO中;
装饰器设计模式(IO流)
对一组对象的功能进行增强时,就可以使用该模式进行问题的解决
好处:弱耦合,被装饰类的变化与装饰类的变化无关
特点:装饰类和被装饰类都必须属于同一接口或者父类
interface Coder {
publicvoid code();
}
classStudent implements Coder {
@Override
publicvoid code() {
System.out.println("javase");
System.out.println("javaweb");
}
}
classItcastStudent implements Coder {
privateStudent s; //获取到被包装的类的引用
publicItcastStudent(Student s) { //通过构造函数创建对象的时候,传入被包装的对象
this.s= s;
}
@Override
publicvoid code() { //对其原有功能进行升级
s.code();
System.out.println("数据库");
System.out.println("ssh");
System.out.println("安卓");
System.out.println(".....");
}
1.5 网络/IO基础
java网络io编程:从传统的BIO(同步阻塞)到NIO(同步非阻塞)再到AIO(异步非阻塞)
BIO
传统的BIO网络编程的基本模型是C/S模型,即两个进程间的通信;
image.png
采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,他接受到客户的请求之后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流返回应答客户端,线程销毁,即典型的请求--应答通宵模型
NIO编程:
NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程
image.png
创建NIO服务端的主要步骤如下:
1.打开ServerSocketChannel,监听客户端连接
2.绑定监听端口,设置连接为非阻塞模式
3.创建Reactor线程,创建多路复用器并启动线程
- 将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件
5.Selector轮询准备就绪的key - Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,简历物理链路
7.设置客户端链路为非阻塞模式
8.将新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送的网络消息
9.异步读取客户端消息到缓冲区
10.对Buffer编解码,处理半包消息,将解码成功的消息封装成Task
11.将应答消息编码为Buffer,调用SocketChannel的write将消息异步发送给客户端
AIO编程:
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数
Java对BIO、NIO、AIO的支持:
- Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
二.spring篇
servlet执行流程:
客户端发出HTTP请求,Web服务器将请求转发到servlet容器,servlet容器解析URL并根据web.xml找到相对应的servlet,并将request,response对象传递给找到的servlet,servlet根据request可以知道是谁发出的请求,请求信息及其他信息,当servlet处理完业务逻辑之后将信息放入到response中并返回客户端。
springMVC的执行流程:
springMVC是由
DIspatchServlrt
为核心的分层控制框架,首先客户端发出一个请求,web服务器解析请求URL并去匹配dispatchServlet的映射URL,如果匹配上就将这个请求放入dispatchServlet,dispatchServlet根据mapping映射配置去寻找相对应的handel
,然后把处理权交给handle,handle封装了处理业务逻辑的代码,当handle处理完成之后会返回一个逻辑视图modelandview
给dispatchServlet,此时的modelandview是一个逻辑视图而不是一个正式视图,所以dispatchServlet会通过viewresource
视图资源去解析modelandview,然后将解析的参数放到view中并返回客户端进行展现。
Aop与IOC概念:
a)IOC:spring是开源框架,使用框架可以减少工作量,提高工作效率并且是分层结构,即相对应的层处理相对应的业务逻辑,减少代码的耦合度。而spring的核心是IOC控制反转和AOP面向切面编程。IOC控制反转主要强调的是程序之间的关系是由容器控制的,容器控制对象,控制了对外部资源的获取。而反转即为,在传统的编程中都是由我们创建对象获取依赖对象,而在IOC是容器帮我们创建对象并注入依赖对象,正是容器帮我们查找和注入对象,对象是被获取,所以叫反转;
b)AOP:面向切面编程,主要是管理系统层的业务,比如日志,权限,事务等,AOP是将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装成一个可重用的模块,这个模块被命名为切面,切面将那些与业务逻辑无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统的重复代码,降低了代码的耦合度,同时提高了系统的可维护性;
三.多线程篇
创建线程的三种方式优缺点
一、继承Thread类创建线程类
1.重写run方法。该run()方法的方法体就代表了线程需要完成的任务。
2.创建Thread子类的实例。
3.调用线程对象的start()方法来启动该线程。
public class SuperTest extends Thread{
public void run(){
System.out.println(getName());
}
public static void main(String[] args) {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
new SuperTest().start();
new SuperTest().start();
}
}
}
二、实现Runnable接口创建线程类
1.定义Runnable的实现类,重写run()方法。
2.创建Runnable实现类的实例,并以此作为Thread的target来创建对象,该对象才是真正的线程对象。
public class Test implements Runnable {
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20){
Test test=new Test();
new Thread(test,"新线程1").start();
new Thread(test,"新线程2").start();
}
}
}
}
三、使用Callable和Future创建线程
1.创建Callable接口的实现类,并实现Call()方法,该方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例。从Java8开始,可以直接使用Lambda表达式创建Callable对象。
2.使用FutureTask来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。
3.使用FutureTask对象作为Thread对象的target创建并启动新线程。
4.调用FutureTask对象的get()方法来获取子线程执行结束后的返回值。
Java 中 sleep 方法和 wait 方法的区别?
sleep来自Thread类,和wait来自Object类
- 调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
- sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒
Java中线程池实现原理:
Java线程池的工厂类:Executors类
初始化四种类型的线程池:
1.newFixedThreadPool:
说明:初始化一个指定线程数的线程池,其中corePoolSize = maxiPoolSize,使用
linkedBlockingQueue
作为阻塞队列;
特点:即使当线程池没有可执行任务时,也不会释放线程;
2.newCachedThreadPool:
说明:初始化一个可缓存的线程池,默认缓存60秒,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue最为阻塞队列;
特点:在没有任务执行时,当线程的空闲时间超过keepALiveTime,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
因此,使用时要注意控制并发的任务数,防止因创建大量的线程而导致降低性能;
3.newSingleThreadPool:
说明:初始化只有一个线程的线程池,内部使用linkedblockingQueue作为阻塞队列;
特点:如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以确保所提交的任务按顺序执行;
4.newScheduledThreadPool:
特点:初始化得线程可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据;
总结:除了newScheduledThreadPool的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。
ThreadPoolExecutor内部具体实现:
ThreadPoolExecutor类构造器语法形式:
ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory,handle);
方法参数:
corePoolSize:核心线程数
maxPoolSize:最大线程数
keepAliveTime:线程存活时间(在corePore<*<maxPoolSize情况下有用)
timeUnit:存活时间的时间单位
workQueue:阻塞队列(用来保存等待被执行的任务)
注:关于workQueue参数的取值,JDK提供了4种阻塞队列类型供选择:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于
SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于ArrayBlockingQuene;
PriorityBlockingQuene:具有优先级的无界阻塞队列;
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,有以下四种取值
注: 当线程池的饱和策略,当阻塞队列满了(阻塞队列最大值:Integer.MAX_VALUE),且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了
4种策略
:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
线程池的五种状态:
其中AtomicInteger变量ctl的功能非常强大:利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态:
1、RUNNING:-1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
2、SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
3、STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
4、TIDYING : 2 << COUNT_BITS,即高3位为010,该状态表示线程池对线程进行整理优化;
5、TERMINATED: 3 << COUNT_BITS,即高3位为011,该状态表示线程池停止工作;
向线程池提交任务(2种):
Executor.execute(Runnable command);
ExecutorService.submit(Callable<T> task);
execute()的内部实现:
1.首次通过workCountof()获知当前线程池中的线程数,如果小于corePoolSize,就通过addWorker()创建线程并执行任务,否则,将该任务放入阻塞队列;
2.如果能成功将任务放入阻塞队列中,如果当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject(),如果当前线程池处于RUNNING状态,则需要再次检查线程池(因为可能在上次检查后,有线程资源被释放),是否有空闲的线程;如果有则执行该任务;
3、如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么将通过addWoker()尝试创建一个新的线程去执行这个任务;如果addWoker()执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;
sumbit()内部实现
会将提交的Callable任务会被封装成了一个FutureTask对象
FutureTask类实现了Runnable接口,这样就可以通过Executor.execute()提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法;
两者比较:
两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
线程池的关闭(2种)
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize()
总结:
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果阻塞队列满了,那就创建新的线程执行当前任务;直到线程池中的线程数达到maxPoolSize,这时再有任务来,只能执行reject()处理该任务;
四.网络协议篇
三次握手和四次挥手、为什么挥手需要四次
三次挥手:
第一次。A跟B说,我要建立连接了。
第二次。B跟A说,OK,那我也建立连接。
第三次。A跟B说,嗯,我知道了。
第二次和第三次都是为了保证连接是可靠的。
假设只有一次握手,而A的包无法发到B那里去,那A就是自顾自的建立了连接,傻傻的发信息,却不知道对方其实根本收不到。所以第二次握手是为了告诉A,B收到了你的信息。
假设只有两次握手,那么对B来说,B是不知道A是否收到了自己的信息的,第三次握手是为了告诉B,A收到了B的信息了,并且可以互发信息了。 B真的需要知道 A是否收到了自己发出的第二次握手的信息吗?是的,如果A的第一次握手因为某些原因延迟很久才到B,而其实现在A和B已经聊完天,关闭连接了,这时B发出第二次握手,而A已经没什么想跟B说的,就不会发出第三次握手,这样B就不会建立连接消耗资源。若是只有二次握手的这种情况,B会直接建立连接消耗资源。
四次挥手
第一次。A跟B说,我要断开连接了。
第二次。B跟A说,好的,我知道了,我不再接收你的信息了。
第三次。B跟A说,我传给你的信息传完了,你可以关闭连接了。
第四次。A跟B说,好的,我关闭连接了。
第二次挥手是为了告诉A,B知道你不再发送信息了,且B不再接收信息。但B仍可以向A发送信息。因为A是主动关闭的一方,但B可能仍然有信息未发送完。
第三次挥手是为了告诉A,B的信息发完了,A你可以关闭连接了。
第四次挥手是为了告诉B,A我知道可以关闭连接了,你也可以关闭了。对B来说,第四次挥手B才知道A成功关闭连接了,不关闭很耗费资源,所以要保证关闭了。若B接收不到第四次挥手信息,将会继续第三次挥手,直到收到确认信息为止。
注意:对A来说,第四次挥手后2MSL内未接收到B的第三次挥手信息才会关闭连接,否则会继续第四次挥手。防止B接收不到第四次挥手信息。(若B接收不到第四次挥手信息,将重复发送第三次挥手信息,这个2MSL就是如果真的有重复发送的第三次挥手信息,在这个时间内肯定到达A了(路由不出问题的话),就是为了保证B收到第四次挥手信息)
五. tomcat 调优篇
Tomcat服务器优化:
a)内存优化:主要是对Tomcat启动参数进行优化,可以在启动脚本中修改Tomcat的最大内存数等;
b)线程数优化:Tomcat的并发连接参数,主要在Tomcat的配置文件server.xml中进行配置,比如修改最小空闲连接线程数,用于提高系统处理性能等等;
c) 优化缓存:打开压缩功能,修改参数,比如压缩的输出内容大小默认为2KB,可以适当的修改。
六.数据库篇
MySQL 索引使用的注意事项
说说分库与分表设计
场景:有时数据库可能既面临着高并发访问的压力,又需要面对海量数据的存储问题,这时需要对数据库既采用分表策略,又采用分库策略,以便同时扩展系统的并发处理能力,以及提升单表的查询性能,这就是所谓的分库分表。
分库分表的策略比前面的仅分库或者仅分表的策略要更为复杂,一种分库分表的路由策略如下:
- 中间变量 = user_id % (分库数量 * 每个库的表数量)
- 库 = 取整数 (中间变量 / 每个库的表数量)
- 表 = 中间变量 % 每个库的表数量
七.linux篇
常用linux命令总结:
查看当前进程:ps
执行退出:exit
查看当前路径:pwd
切换目录:cd
清屏:clear
创建目录: mkdir
移动文件:mv
复制文件:cp
vi 文件名 #编辑方式查看,可修改
cat 文件名 #显示全部文件内容
more 文件名 #分页显示文件内容
less 文件名 #与 more 相似,更好的是可以往前翻页
tail 文件名 #仅查看尾部,还可以指定行数
head 文件名 #仅查看头部,还可以指定行数
八.微服务篇
九.解决方案篇
海量数据的解决方案:
- 使用缓存;
- 页面静态化技术;
- 数据库优化;
- 分离数据库中活跃的数据;
- 批量读取和延迟修改;
- 读写分离;
- 使用NoSQL和Hadoop等技术;
- 分布式部署数据库;
- 应用服务和数据服务分离;
- 使用搜索引擎搜索数据库中的数据;
- 进行业务的拆分;
高并发情况下的解决方案:
- 应用程序和静态资源文件进行分离;
- 页面缓存;
- 集群与分布式;
- 反向代理;
- CDN;
参考:https://blog.csdn.net/zhaojw_420/article/details/70881333
网友评论