java 知识点整理

时间:2022-11-17 15:17:47

Java运行时数据区

1.程序计数器:当前线程所执行的字节码行号的指示器。
java虚拟机多线程是通过线程间轮流切换来分配给处理器执行时间;在确定时间节点,一个处理器(一核)只会执行一个线程的指令;为保证 线程切换 回来后能恢复到原执行位置,各个线程间计数器互相不影响,独立存储
如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。
2.:Java方法执行的内存模型。
Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈
栈帧中包括局部变量表、操作数栈、指向当前方法所属类的运行时常量池的引用、方法返回地址和一些额外的附加信息。
java 知识点整理
局部变量表:就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及方法形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
操作数栈:最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
指向运行时常量池的引用: 因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量池。
方法返回地址: 当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
3.本地方法:栈是执行Java方法的,而本地方法栈是用来执行native方法的
4.:主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,是垃圾收集器管理的主要区域
5.方法区:主要用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、字段信息)、常量、静态变量、编译器编译后的代码等.
在方法区中存在一个叫运行时常量池的区域,它主要用于存放编译器生成的各种字面量和符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);这些内容将在类加载后存放到运行时常量池中,以便后续使用。它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
java 知识点整理
一个指向A的class对象,一个指向加载自己的classLoader

栈是编译时分配空间,而堆是动态分配(运行时分配空间),所以栈的速度快cpu有专门的寄存器(esp,ebp)来操作栈,堆都是使用间接寻址的。栈快点
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。

Java类加载

1.类加载子系统的作用:根据给定的全限定名类名(如java.lang.Object)来装载class文件的内容到运行时数据区中的方法区
2.双亲委派机制:当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
3.类加载过程:加载、链接(验证、准备、解析)、初始化
加载阶段,虚拟机需要完成以下3件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
验证阶段大致会完成4个阶段的检验动作:
1.文件格式验证
2.元数据验证
3.字节码验证:
4. 符号引用验证
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化阶段,则根据程序猿通过程序制定的主观计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器()方法的过程.

JVM调优

栈的内存要远远小于堆内存
-Xss选项设置栈内存的大小。
-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。

锁(lock、synchronized)

在Java中,每一个对象都拥有一个锁标记(锁),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完后,系统会自动让线程释放对锁的占用;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)响应中断Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)Lock可以是否获取锁,synchronized不行。
5)Lock可以提高多个线程进行读操作的效率。
6) 公平锁Lock可以设置为公平锁,synchronized就是非公平锁

单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的(线程B调用threadB.interrupt()方法能够中断线程B的等待过程
)。
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

线程

线程共有5中状态:新建、可运行(runnable)、运行(running),阻塞和死亡。
当使用new操作符创建新线程时,线程处于“新建”状态。
当调用start()方法时,线程处于可运行状态。
阻塞状态是指线程因为某种原因放弃了cpu 使用权,即让出了cpu分片时间,暂时停止运行。直到线程进入可运行状态,才有机会再次获得cpu时间 转到运行状态。
阻塞的情况分三种:
(一). 等待阻塞:运行的线程执行o.wait()方法,JVM会把该线程放入等待队列中。
(二). 同步阻塞:运行的线程在获取对象的同步锁时,该锁正被其他线程拥有,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
当run()方法运行完毕或出现异常时,线程处于终止状态。
java 知识点整理

1)start方法:start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
2)run方法:run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
3)sleep方法:sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,即使调用sleep方法,其他线程也无法访问这个对象。
注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
4)yield方法:调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
  注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
sleep() 方法在指定的睡眠时间内一定不会再得到运行机会,直到它的睡眠时间完成;而 yield() 方法让出控制权后,还有可能马上被系统的调度机制选中来运行,比如,执行yield()方法的线程优先级高于其他的线程,那么这个线程即使执行了 yield() 方法也可能不能起到让出CPU控制权的效果,因为它让出控制权后,进入排队队列,调度机制将从等待运行的线程队列中选出一个等级最高的线程来运行,那么它又(很可能)被选中来运行。
5)join方法:假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。
当调用thread1.join()方法后,main线程会进入等待,然后等待thread1执行完之后再继续执行。
  实际上调用join方法是调用了Object的wait方法,这个可以通过查看源码得知。join方法同样会让线程释放对一个对象持有的锁。
wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
6)interrupt方法:单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。
interrupt方法可以中断处于阻塞状态的线程。不能中断正在运行中的线程。但是如果配合isInterrupted()能够中断正在运行的线程,因为调用interrupt方法相当于将中断标志位置为true,那么可以通过调用isInterrupted()判断中断标志是否被置位来中断线程的执行。
wait()和sleep
1.sleep是Thread类的方法,是线程用来控制自身流程的;wait是Object类的方法,用来线程间的通信,这个方法会使当前拥有该对象锁的线程等待直到其他线程调用notify方法时再醒来,不过你也可以给他指定一个时间,自动醒来。这个方法主要是用走不同线程之间的调度的。
2.调用某个对象的wait()方法,相当于让当前线程交出此对象的锁,然后进入等待状态,等待后续再次获得此对象的锁;Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁;
调用sleep方法不会释放锁,调用wait方法会释放当前线程的锁(其实线程间的通信是靠对象来管理的,所有操作一个对象的线程是这个对象通过自己的wait方法来管理的,就好像这个对象是电视机,三个人是三个线程,那么电视机的遥控器就是这个锁,假如现在A拿着遥控器,电视机调用wait方法,那么A就交出自己的遥控器,由jVM虚拟机调度,遥控器该交给谁。)

wait()、notify()和notifyAll()方法
利用wait()来让一个线程在某些条件下暂停运行。例如,在生产者消费者模型中,生产者线程在缓冲区为满的时候,消费者在缓冲区为空的时候,都应该暂停运行。如果某些线程在等待某些条件触发,那当那些条件为真时,你可以用 notify 和 notifyAll 来通知那些等待中的线程重新开始运行。

wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用
用wait和notify解决生产者消费者问题。对在多线程间共享的那个Object来使用wait。在生产者消费者问题中,这个共享的Object就是那个缓冲区队列。希望上锁的对象就应该被synchronized,即那个在多个线程间被共享的对象。在生产者消费者问题中,应该被synchronized的就是那个缓冲区队列。
java 知识点整理

1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。为了调用wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()。一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。
2)调用某个对象的wait()方法能让当前线程阻塞
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的锁的线程,如果有多个线程都在等待这个对象的锁,则只能唤醒其中一个线程;
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的锁的线程;

一个线程被唤醒不代表立即获取了对象的锁,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

为何这三个不是Thread类声明中的方法,而是Object类中声明的方法?
由于每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个对象的锁,如果通过线程来操作,就非常复杂了。

http://www.cnblogs.com/dolphin0520/p/3920385.html
http://www.importnew.com/16453.html

Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。
阻塞队列实际上是使用了Condition来模拟线程间协作。
Condition是个接口,基本的方法就是await()和signal()方法;
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
java 知识点整理
java 知识点整理

BlockingQueue

BlockingQueue通常用于一个线程生产对象,而另外一个线程消费这些对象的场景,一个线程往里边放,另外一个线程从里边取的一个BlockingQueue

非阻塞队列:不会对当前线程产生阻塞,面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。
阻塞队列:会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。

阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。

具体的实现类有LinkedBlockingQueue,ArrayBlockingQueued等。一般其内部的都是通过Lock和Condition来实现阻塞和唤醒。
java 知识点整理

volatile

 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。

CAS

CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术。
CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。

多线程

多线程的优点:
1)解决多任务同时执行的需求,合理使用CPU资源
2)程序设计在某些情况下更简单
3)程序响应更快
多线程的代价:
1)设计更复杂
在多线程访问共享数据的时候,这部分代码需要特别的注意。
2)上下文切换的开销
当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。
3)增加资源消耗
线程在运行的时候需要从计算机里面得到一些资源。除了CPU,线程还需要一些内存来维持它本地的堆栈。

合理的配置线程池的大小

CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,
IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,

线程池的作用:线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;
少了浪费了系统资源,多了造成系统拥挤效率不高。

线程池

● 等待队列:顾名思义,就是你调用线程池对象的submit()方法或者execute()方法,要求线程池运行的任务(这些任务必须实现Runnable接口或者Callable接口)。但是出于某些原因线程池并没有马上运行这些任务,而是送入一个队列等待执行。
● 核心线程:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是你创建线程时所设置的corePoolSize参数决定的。如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段)。
● 非核心线程:一旦任务数量过多(由等待队列的特性决定),线程池将创建“非核心线程”临时帮助运行任务。你设置的大于corePoolSize参数小于maximumPoolSize参数的部分,就是线程池可以临时创建的“非核心线程”的最大数量。这种情况下如果某个线程没有运行任何任务,在等待keepAliveTime时间后,这个线程将会被销毁,直到线程池的线程数量重新达到corePoolSize。
● maximumPoolSize参数也是当前线程池允许创建的最大线程数量。那么如果设置的corePoolSize参数和设置的maximumPoolSize参数一致时,线程池在任何情况下都不会回收空闲线程。keepAliveTime和timeUnit也就失去了意义。
● keepAliveTime参数和timeUnit参数也是配合使用的。keepAliveTime参数指明等待时间的量化值,timeUnit指明量化值单位。例如keepAliveTime=1,timeUnit为TimeUnit.MINUTES,代表空闲线程的回收阀值为1分钟。

线程池工作原理

1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2、当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
   b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
   c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
   d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
   这样的过程说明,并不是先加入任务就一定会先执行。假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。

https://blog.csdn.net/lipc_/article/details/52025993

线程池的等待队列

工作队列的默认选项是 SynchronousQueue
在使用ThreadPoolExecutor线程池的时候,需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue;

排队有三种通用策略:
1.直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求* maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许*线程具有增长的可能性。
2.*队列。使用*队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用*队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许*线程具有增长的可能性。
3.有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

有return的try catch finally

任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,编译器把finally中的return实现为一个warning。

如果有 finally 代码块,此时程序执行到 try 代码块里的 return 语句之时并不会立即执行 return(如果return后是语句或者函数,eg:return b+=10; or return functionA();,先执行return后的语句或者函数),而再去执行 finally 代码块里的代码,
若 finally 代码块里没有 return 或没有能够终止程序的代码,程序将在执行完 finally 代码块代码之后再返回 try 代码块执行 return 语句来结束整个方法;
若 finally 代码块里有 return 或含有能够终止程序的代码,方法将在执行完 finally 之后被结束,不再跳回 try 代码块执行 return。

HashMap

HashMap的底层主要是基于数组和链表来实现的

通过key的hashCode来计算hash值,再通过Hash值去计算存储在数组中的下标【hash(key.hashCode()),h&(length-1)】,进行少量比较即可得到元素,这使得 HashMap 的查找效率贼高。

哈希表的容量是2的整数次幂:length-1得到的二进制数的每个位上的值都为1
,保证了散列的均匀;同时h&(length-1)就相当于对length取模,提升了效率;

put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:
如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。
如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。  
如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——

GC算法

标记-清除算:容易产生碎片
复制算法:每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。
标记-整理算法:标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
分代收集算法:新生采用复制算法,老生代采用标记整理算法

HTTP协议

是基于TCP的应用层协议,主要是用来规定客户端和服务端数据传输的格式和数据交互行为,并不负责数据传输的细节。

NIO

NIO 是一种同步非阻塞的 IO 模型。
同步是指线程不断轮询 IO 事件是否就绪
非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。
同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;
非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

基于阻塞式I/O的多线程模型中,Server为每个Client连接创建一个处理线程,每个处理线程阻塞式等待可能达到的数据,一旦数据到达,则立即处理请求、返回处理结果并再次进入等待状态。

索引

优点:1.加快数据的检索速度;
2.创建唯一性索引,保证数据记录的唯一性;
3.加速表和表之间的连接;
4.在使用分组和排序子句进行数据检索时,可以减少查询中分组和排序的时间。
缺点:索引需要占物理空间,当对表中的数据进行增删改的时候,索引也要动态的维护,降低了数据的维护速度。

SELECT * FROM EMP WHERE EXISTS (SELECT ‘X’ FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB’)

示例一、查询出每个部门的编号,名称,位置,部门人数,平均工资
SELECT d.deptno,d.dname,d.loc,temp.con,temp.avgsal FROM dept d,(SELECT deptno dno,COUNT(empno) con,ROUND (AVG(sal),2) avgsal FROM emp GROUP BY deptno) temp WHERE d.deptno=temp.dno;

示例二、查询出所有在部门SALES(销售部)工作的员工编号,姓名,基本工资,奖金,职位,入职日期,部门最高和最低工资
SELECT e.empno,e.ename,e.sal,e.comm,e.job,e.hiredate,e.deptno,temp.maxsal,temp.minsal FROM emp e,( SELECT deptno dno,MAX(sal) maxsal,MIN(sal) minsal FROM emp GROUP BY deptno ) temp WHERE e.deptno=(SELECT deptno FROM dept WHERE dname=’SALES’) AND e.deptno=temp.dno;

Springboot

提供starter简化Maven配置
自动配置
内嵌Servlet容器
Spring Boot中使用yaml文件或者properties文件进行配置管理,大大简化了之前的xml文件的繁琐配置,并且主要都是通过注解来进行开发。

springboot自动配置的原理

在spring程序main方法中 添加@SpringBootApplication或者@EnableAutoConfiguration
会自动去maven中读取每个starter中的spring.factories文件
该文件里配置了所有需要被创建spring容器中的bean

SpringMVC流程

1、用户发送请求至前端控制器DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。执行链(HandlerExecutionChain包含处理器对象及处理器拦截器)
4、DispatcherServlet调用HandlerAdapter处理器适配器
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、ViewReslover解析后返回具体View
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户

AOP

AOP应用:事务、缓存、日志、性能等
使用spring的声明式事务后,只需要在数据库处理方法上注解事务,就可以对操作进行管理,事务的设置和逻辑代码分开,
 1、Aspect(切面) : 应用运行过程中的关注点,关注点可以横切多个对象,被称为横切关注点。
  2、pointcut(切入点):可插入增强处理的连接点。
  3、joinpoint(连接点):程序执行过程中明确的点,如方法的调用,或者异常的抛出。
  4、advice(增强处理):AOP框架特定的切入点执行的增强处理

在AOP编程,我们需要做如下三部分:
  1、定义普通组件。
  2、定义切入点、一个切入点可能横切多个业务组件。
  3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作。

static

static 表示静态或全局,它可以修饰属性,方法和代码块。由于静态属性和方法是属于该类的所有对象的,所以可以用类名.静态属性/方法名来访问。用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块。
类成员变量分两种:static修饰的变量,叫静态变量或类变量;没有被static修饰的变量,叫实例变量。类中的实例变量是在创建对象时被初始化的,被static修饰的属性,也就是类变量,是在类加载时被创建并进行初始化,类加载的过程是进行一次。也就是类变量只会被创建一次。

this关键字必须放在非静态方法里面,this关键字代表自身,的使用用途:引用成员变量、成员方法,在自身构造方法内部引用其它构造方法,代表自身类的对象

类初始化主动引用

1.创建类的实例
2.访问类的静态变量(除常量【被final修辞的静态变量】
3.访问类的静态方法
4.反射如(Class.forName(“my.xyz.Test”))
5.当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
6.虚拟机启动时,定义了main()方法的那个类先初始化

被动引用
1.子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。对于静态字段,只有直接定义这个字段的类才会被初始化.
2.通过数组定义来引用类,不会触发类的初始化
3.访问类的常量,不会初始化类