版本:1.0.0
最后更新时间:2023年3月19日
首先,所谓面向对象,其实是指软件工程中的一类编程风格,很多人称呼他们为开发范式、编程泛型(Programming Paradigm)。面向对象是众多开发范式中的一种。除了面向对象以外,还有面向过程、指令式编程、函数式编程等。
虽然这几年函数式编程越来越被人们所熟知,但是,在所有的开发范式中,我们接触最多的主要还是面向过程和面向对象两种。
面向过程(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。最典型的面向过程的编程语言就是C语言。
简单来说,面向过程的开发范式中,程序员需要把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
面向过程进行的软件开发,其代码都是流程化的,很明确的可以看出第一步做什么、第二步做什么。这种方式的代码执行起来效率很高。
但是,面向过程同时存在着代码重用性低,扩展能力差,后期维护难度比较大等问题。
Java中有8种基本数据类型分为三大类。
char
boolean
1、整型:byte、short、int、long
2、浮点型:float、double
String不是基本数据类型,是引用类型。
Java中的整型主要包含byte、short、int和long这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。
byte:byte用1个字节来存储,范围为-128(-27)到127(27-1),在变量初始化的时候,byte类型的默认值为0。
short:short用2个字节存储,范围为-32,768 (-2^15)到32,767 (2^15-1),在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0。
int:int用4个字节存储,范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0。
long:long用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为0。
单精度浮点数在计算机存储器中占用4个字节(32 bits),利用“浮点”(浮动小数点)的方法,可以表示一个范围很大的数值。
比起单精度浮点数,双精度浮点数(double)使用 64 位(8字节) 来存储一个浮点数。
由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。
建议使用BigDecimal或者Long(单位为分)来表示金额。
Java 中的自动拆箱与自动装箱的有关知识。
Java 语言是一个面向对象的语言,但是 Java 中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
在这八个类名中,除了 Integer 和 Character 类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。
因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
我们认为包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于 boxing,中文翻译为装箱。
反之,把包装类转换成基本数据类型的过程就是拆包装,英文对应于 unboxing,中文翻译为拆箱。
自动装箱: 就是将基本数据类型自动转换成对应的包装类。
自动拆箱:就是将包装类自动转换成对应的基本数据类型。
自动装箱都是通过包装类的 valueOf()
方法来实现的.自动拆箱都是通过包装类对象的 xxxValue()
来实现的。
isXXX()
和setXXX()
形式的。getXXX()
和setXXX()
形式的。阿里巴巴Java开发手册:POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误。
在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success!
Boolean类型的变量的默认值是null,boolean类型的默认值为false。
阿里巴巴Java开发手册:
关于基本数据类型与包装数据类型的使用标准如下:
1)【强制】所有的POJO类属性必须使用包装数据类型。
2)【强制】PRC方法的返回值和参数必须使用包装数据类型。
3)【推荐】所有的局部变量使用基本数据类型。
说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
首先,我们需要知道什么是不可变对象?
不可变对象是在完全创建后在其内部状态保持不变的对象。这意味着,一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。
一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
如果我们想要一个可修改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。
replace、replaceAll和replaceFirst是Java中常用的替换字符的方法,它们的方法定义是:
replace(CharSequence target, CharSequence replacement) ,用replacement替换所有的target,两个参数都是字符串。
replaceAll(String regex, String replacement) ,用replacement替换所有的regex匹配项,regex很明显是个正则表达式,replacement是字符串。
replaceFirst(String regex, String replacement) ,基本和replaceAll相同,区别是只替换第一个匹配项。
可以看到,其中replaceAll以及replaceFirst是和正则表达式有关的,而replace和正则表达式无关。
replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是替换所有匹配的字符,而replaceFirst()仅替换第一次出现的字符
运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。但是,String的使用+字符串拼接也不全都是基于StringBuilder.append,还有种特殊情况,那就是如果是两个固定的字面量拼接。
+
拼接字符串直接使用StringBuilder
的方式是效率最高的。因为StringBuilder
天生就是设计来定义可变字符串和字符串的变化操作的。
但是,还要强调的是:
1、如果不是在循环体中进行字符串拼接的话,直接使用+
就好了。
2、如果在并发场景中进行字符串拼接的话,要使用StringBuffer
来代替StringBuilder
。
String.valueOf也是调用Integer.toString来实现的。
在JVM中,为了减少相同的字符串的重复创建,为了达到节省内存的目的。会单独开辟一块内存,用于保存字符串常量,这个内存区域被叫做字符串常量池。
当代码中出现双引号形式(字面量)创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。
在JDK 7以前的版本中,字符串常量池是放在永久代中的。
因为按照计划,JDK会在后续的版本中通过元空间来代替永久代,所以首先在JDK 7中,将字符串常量池先从永久代中移出,暂时放到了堆内存中。
在JDK 8中,彻底移除了永久代,使用元空间替代了永久代,于是字符串常量池再次从堆内存移动到永久代中
谈到常量池,在Java体系中,共用三种常量池。分别是字符串常量池、Class常量池和运行时常量池。
Java语言中负责编译出字节码的编译器是一个命令是javac
。
javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。
如何使用16进制打开class文件:使用
vim test.class
,然后在交互模式下,输入:%!xxd
即可。
Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。要想能够读懂上面的字节码,需要了解Class类文件的结构。
我们需要知道的是,在Class文件的4个字节的魔数后面的分别是4个字节的Class文件的版本号(第5、6个字节是次版本号,第7、8个字节是主版本号。在版本号后面的,就是Class常量池入口了。
Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
由于不同的Class文件中包含的常量的个数是不固定的,所以在Class文件的常量池入口处会设置两个字节的常量池容量计数器,记录了常量池中常量的个数。
常量池中主要存放两大类常量:字面量(literal)和符号引用(symbolic references)
在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
以上是关于计算机科学中关于字面量的解释,并不是很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。
符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符
首先,可以明确的是,Class常量池是Class文件中的资源仓库,其中保存了各种常量。而这些常量都是开发者定义出来,需要在程序的运行期使用的。
运行时常量池( Runtime Constant Pool)是每一个类或接口的常量池( Constant_Pool)的运行时表示形式。
它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表( SymbolTable)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。
每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。
以上,是Java虚拟机规范中关于运行时常量池的定义。
运行时常量池中包含了若干种不同的常量:
编译期可知的字面量和符号引用(来自Class常量池) 运行期解析后可获得的常量(如String的intern方法)
所以,运行时常量池中的内容包含:Class常量池中的常量、字符串常量池中的内容
虚拟机启动过程中,会将各个Class文件中的常量池载入到运行时常量池中。
所以, Class常量池只是一个媒介场所。在JVM真的运行时,需要把常量池中的常量加载到内存中,进入到运行时常量池。
字符串常量池可以理解为运行时常量池分出来的部分。加载时,对于class的静态常量池,如果字符串会被装到字符串常量池中。
字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过65535,并且在javac执行过程中控制了最大值为65534。
在运行期,长度不能超过Int的范围,否则会抛异常。
try-catch
配合使用,表示无论是否处理异常,总是执行 finally 块中的代码。main()
方法必须声明为 public。Collection 是一个集合接口。 它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。是list,set等的父接口。
Collections 是一个包装类。 它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
日常开发中,不仅要了解Java中的Collection及其子类的用法,还要了解Collections用法。可以提升很多处理集合类的效率。
List,Set都是继承自Collection接口。都是用来存储一组相同类型的元素的。
List特点:元素有放入顺序,元素可重复 。有顺序,即先放入的元素排在前面。
Set特点:元素无放入顺序,元素不可重复。无顺序,即先放入的元素不一定排在前面。 不可重复,即相同元素在set中只会保留一份。所以,有些场景下,set可以用来去重。 不过需要注意的是,set在元素插入时是要有一定的方法来判断元素是否重复的。这个方法很重要,决定了set中可以保存哪些元素。
List主要有ArrayList、LinkedList与Vector几种实现。
这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.
Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.
注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入 null值
2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入 null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。
TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。
反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有属性和方法。
在运行时判断任意一个对象所属的类。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时任意调用一个对象的方法。
在运行时构造任意一个类的对象。
反射是Java语言的一个特性,它允许程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个Java类获取它所有的成员变量和方法并且显示出来。
反射主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。在Java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码链接。但是反射使用不当会成本很高!类中有什么信息,利用反射机制就能可以获得什么信息,不过前提是得知道类的名字。
1、在运行时判断任意一个对象所属的类;
2、在运行时获取类的对象;
3、在运行时访问java对象的属性,方法,构造方法等。
首先要搞清楚为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念。
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,用以降低类之间的藕合性。
反射机制的优点:可以实现动态创建对象和编译,体现出很大的灵活性(特别是在J2EE的开发中它的灵活性就表现的十分明显)。通过反射机制我们可以获得类的各种内容,进行反编译。对于JAVA这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。
反射机制的缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且让它满足我们的要求。这类操作总是慢于直接执行相同的操作。
1)软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
2)由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
3)具体到IOC框架产品(比如Spring)来讲,需要进行大量的配置工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
4)IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
所谓静态代理,就是代理类是由程序员自己编写的,在编译期就确定好了的。
静态代理的用途:
1.控制真实对象的访问权限:通过代理对象控制真实对象的使用权限。
2.避免创建大对象:通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
3.增强真实对象的功能:这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。
动态代理中的代理类并不要求在编译期就确定,而是可以在运行期动态生成,从而实现对目标对象的代理功能。
反射是动态代理的一种实现方式。
反射是动态代理的一种实现方式。
Java中,实现动态代理有两种方式:
1、JDK动态代理:java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。
2、Cglib动态代理:Cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
JDK动态代理和Cglib动态代理的区别:
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
Cglib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
Cglib与动态代理最大的区别就是:
使用动态代理的对象必须实现一个或多个接口
使用cglib代理的对象则无需实现接口,达到代理类无侵入。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
序列化是将对象转换为可传输格式的过程。 是一种数据的持久化手段。一般广泛应用于网络传输,RMI和RPC等场景中。
反序列化是序列化的逆操作。
序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。
类通过实现 java.io.Serializable
接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException
。
如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成java.io.Serializable
接口。
说简单点,就是 定义其他注解的注解 。 比如Override这个注解,就不是一个元注解。而是通过元注解定义出来的。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这里面的 @Target @Retention 就是元注解。
元注解有六个:@Target(表示该注解可以用于什么地方)、@Retention(表示再什么级别保存该注解信息)、@Documented(将此注解包含再javadoc中)、@Inherited(允许子类继承父类中的注解)、@Repeatable(1.8新增,允许一个注解在一个元素上使用多次)、@Native(1.8新增,修饰成员变量,表示这个变量可以被本地代码引用,常常被代码生成工具使用)。
除了元注解,都是自定义注解。通过元注解定义出来的注解。 如我们常用的Override 、Autowire等。 日常开发中也可以自定义一个注解,这些都是自定义注解。
在Java中,类使用class定义,接口使用interface定义,注解和接口的定义差不多,增加了一个@符号,即@interface,代码如下:
public @interface EnableAuth {
}
注解中可以定义成员变量,用于信息的描述,跟接口中方法的定义类似,代码如下:
public @interface EnableAuth {
String name();
}
还可以添加默认值:
public @interface EnableAuth {
String name() default "猿天地";
}
上面的介绍只是完成了自定义注解的第一步,开发中日常使用注解大部分是用在类上,方法上,字段上,示列代码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableAuth {
}
并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
那么,操作系统是如何实现这种并发的呢?
现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是多用户多任务分时操作系统。使用这些操作系统的用户是可以“同时”干多件事的。
但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。
如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。
由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。
所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。
并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户的各个任务使用。
在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。在操作系统中,CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。上下文切换就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。
在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换帧”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
而在多个进程之间切换的时候,需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑,能不能在一个进程中增加一些“子任务”,这样减少上下文切换的成本。比如我们使用Word的时候,它可以同时进行打字、拼写检查、字数统计等,这些子任务之间共用同一个进程资源,但是他们之间的切换不需要进行上下文切换。
在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
随着时间的慢慢发展,人们进一步的切分了进程和线程之间的职责。把进程当做资源分配的基本单元,把线程当做执行的基本单元,同一个进程的多个线程之间共享资源
拿我们比较熟悉的Java语言来说,Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LplvqLT3-1679215794156)(image/167019dc85aaaf5a.jpg)]
虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点–这项操作可以通过设置线程优先级来完成。
Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
Java线程优先级使用1-10的整数表示。默认的优先级是5.
最低优先级 1:Thread.MIN_PRIORITY
最高优先级 10:Thread.MAX_PRIORITY
普通优先级 5:Thread.NORM_PRIORITY
线程安全是编程中的术语,指某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
1、基于SQL语句编程,相当灵活,不会对应于程序或者数据库现有设计造成任何影响,SQL写在XML里,解除SQL与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手工开关连接。
3、很好的与各种数据库兼容。
4、能够与spring很好的集成。
5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
1、SQL预计编写工作量大,尤其当字段多,关联表多时,对开发人员编写SQL语句的功底有一定要求。
2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
#{}是预编译处理,${}是字符串替换
mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值
mybatis在处理 时,就是把 {}时,就是把 时,就是把{}替换成变量的值
使用#{}可以有效的防止SQL注入,提供系统安全性
1、通过查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
2、通过来映射字段名和实体类属性名的一一对应的关系。
mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内置直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数。
1)一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为session,当session flush或close之后,该session中的所有Cache就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源。默认不打开二级缓存,要开启二级缓存属性类需要实现Serializable序列化接口。
Java中的线程分为两种:守护线程和用户线程
任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(boolon);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。
两者的区别:
唯一的区别是判断虚拟机(JVM)何时离开,Daemon是为其他线程服务,如果全部的User Thread已经撤离,Daemon没有可服务的线程,JVM撤离。也可以理解为守护线程是JVM自动创建的线程(但不一定),用户线程是程序创建的线程。
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元
一个程序至少有一个进程,一个进程最少有一个线程。
多线程会共用一组计算机上的CPU,而线程数大于给程序分配的CPU数量时,为了让各个线程都有执行的机会,就需要轮转使用CPU,不同的线程切换使用CPU发生的切换数据就是上下文切换。
采用时间轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。
当一个线程进入wait之后,就需要等其他线程notify/notifyALL,使用notifyALL可以唤醒所有处于wait状态的线程,使其重写进入锁的争夺队列中,而notify只能唤醒一个。
线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
Servlet不是线程安全的,servlet是单实例多线程,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
手写SPring
手写mybatis