JVM
内存模型
堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器线程私有
方法区
所有线程共享的区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据(已经加载的类的名称、修饰符等信息;类中的static变量;类中的field信息;类中定义为final常量;类中的方法信息;运行时常量池(方法区中的一部分):编译器生成的各种字面量和符号引用(编译期)存储在class文件的常量池中,这部分内容会在类加载之后进入运行时常量池 )
方法区同样存在垃圾收集,因为用户通过自定义加载器加载的一些类同样会成为垃圾,JVM会回收一个未被引用类所占的空间,以使方法区的空间达到最小。
堆内存
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆"),所有线程共有,可以分为新生代和老年代。Perm
代表的是永久代(jdk8之后,被替换成了元空间 Metaspace)
GC(垃圾回收器)对年轻代中的对象进行回收被称为Minor GC
,年轻代就是用来存放的年轻的对象(没有经历过多次垃圾回收的对象),如果一个对象经历过了一定次数的Minor GC
,JVM一般就会将这个对象放入到年老代,而JVM对年老代的对象的回收则称为Major GC
。
年轻代中还可以细分为三个部分,大部分对象刚创建的时候,JVM会将其分布到Eden区域;当Eden区域中的对象达到一定的数目的时候,就会进行Minor GC,经历这次垃圾回收后所有存活的对象都会进入两个Suvivor Place中的一个;同一时刻两个Suvivor Place,即s0和s1中总有一个总是空的;年轻代中的对象经历过了多次的垃圾回收就会转移到年老代中,可以通过MaxTenuringThrehold参数来控制。
Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame )用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
该区域可能抛出:1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
程序计数器
当前线程所执行的字节码的行号指示器,用于字节码解释器对字节码指令的执行(字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成)
本地方法栈
与虚拟机栈的作用相似,不过虚拟机栈是为Java方法服务的,而本地方法栈是为Native方法服务的。
JVM 调优参数:
- Xmx:最大堆大小
- Xms:初始堆大小
- Xmn:年轻代大小
- XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值
垃圾回收算法
引用计数法:原理是在此对象有个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只收集计数为0的对象(此算法无法处理循环引用的问题)
标记-清除:此算法分两个阶段,第一阶段从引用的根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除(此算法需要暂停应用,同时产生内存碎片)
复制算法:此算法把内存划分为两个相等的区域,每次只使用一个区域,垃圾回收时,遍历当前使用的区域,把正在使用的对象复制到另一个区域中,算法每次只处理正在使用的对象,因此复制的成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现碎片问题(此算法的缺点也很明显,需要两倍的内存空间)
标记-整理:此算法结合了 标记-清除 和 复制算法 的两个的优点,也是分两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记的对象并且把存活的对象 压缩 到堆的其中一块,按顺序排放(此算法避免 标记-清除 的碎片问题,同时也避免 复制 的空间问题)
垃圾收集器
Concurrent Mark Sweep(CMS)
是一种以获取最短回收停顿时间为目标的收集器,从Mark-Sweep上可以看出,CMS是基于“标记-清除”算法实现的。主要应用于B/S模式的服务端(希望系统停顿时间尽可能短,尤其重视响应时间)
Garbage First(G1)
面向服务端应用的垃圾收集器。G1收集器,Java堆的内存布局是将整个Java堆分为多个大小相等的独立区域(Region),也保留了新生代 和老年代的概念。但是新生代和老年代不再是物理隔离的,它们都是一部分Region的集合。G1跟踪各个Region里面的垃圾堆积的价值大小(也就是回收获得的空间大小以及回收需要的时间的经验值),在后台维护一个优先列表,每次根据允许的的收集时间,优先回收价值最大的Region。
注:释放掉占据的内存空间是由gc完成,但是程序员无法明确强制其运行,该空间在不被引用的时候不一定会立即被释放,这取决于GC本身,无法由程序员通过代码控制。
JVM 类加载过程
1. 加载: 这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
2. 验证: 此阶段的主要目的是确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
3. 准备: 此阶段正式为类变量分配内存并设置类变量的初始值阶段(在方法区中分配这些变量所使用的内存空间)。
4. 解析: 指虚拟机将常量池中的符号引用替换为直接引用的过程(符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中; 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在)。
5. 初始化: 类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。
类加载器(双亲委派模型)
1. 启动类加载器(Bootstrap ClassLoader): 由C++语言实现(针对HotSpot),负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
2. 其他类加载器:由Java
语言实现(继承自抽象类ClassLoader
)
- 扩展类加载器(Extension ClassLoader): 负责加载 JAVA_HOME\lib\ext 目录或通过 java.ext.dirs 系统变量指定路径中的类库。
- 应用程序类加载器(Application ClassLoader): 负责加载用户路径(classpath)上的类库。
JVM通过双亲委派模型进行类的加载,我们也可以通过继承 java.lang.ClassLoader 实现自定义的类加载器。
双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器(工作过程: 如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException
),子加载器才会尝试自己去加载)。
双亲委派模型好处:类随着它的类加载器一起具备了一种带有优先级的层次关系。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。因此,使用双亲委派模型来组织类加载器之间的关系(java.lang.Object 它由启动类加载器加载。双亲委派模型保证任何类加载器收到的对 java.lang.Object 的加载请求,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为 java.lang.Object 的类,并用自定义的类加载器加载,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱)。
Java 基础
基本数据类型(变量在不设置初始值时,会进行默认值赋值;封装类就是引用类型,基本类型就是值类型)
String、StringBuffer、StringBuilde
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量(String对象一旦创建之后该对象不可更改,StringBuilder和 StringBuffer对象是变量,可以更改)
StringBuffer中很多方法可以带有synchronized关键字(线程安全),StringBuilder的方法则没有该关键字(线程不安全)
String:适用于少量的字符串操作的情况;StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况;StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
String对 “+” 的重载
编译器会创建一个 StringBuilder 对象,用来构造最终要生成的 String,并为每一个字符串调用一次 StringBuilder 中的 append() 方法。
replaceFirst、replaceAll、replace
replace:参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换。
replaceAll:参数是regex,基于正则表达式的替换。
replaceFirst:替换第一次出现的字符。
& 与 &&
&&: 会出现短路,如果可以通过第一个表达式判断出整个表达式的结果,则不继续后面表达式的运算(只能操作boolean类型数据)。
&: 不会出现短路,将整个表达式都运算。既可以操作boolean数据还可以操作数。
"==" 和 equals
== : 比较两个对象的内存地址是否相等。
equals:比较两个对象的内容是否相等。
public、protected、private 作用域
外部类:它也可以使用访问控制符修饰,但外部类只能有两种访问控制级别: public 和默认。因为外部类没有处于任何类的内部,也就没有其所在类的内部、所在类的子类两个范围,因此 private 和 protected 访问控制符对外部类没有意义。
内部类:上一级程序单元是外部类,它具有 4 个作用域:public、protected、friendly、private
- 静态内部类:不可访问外部非静态资源;
- 成员内部类:可以访问外部所有资源,但是本身内部不可用静态属性(需要依靠外部类实例化)
- 局部内部类:不可被访问修饰符和 static 修饰,只能访问 final 变量和形参;
- 匿名内部类:只能创建匿名内部类的一个实例(没有构造器;没有静态资源;无法被访问修饰符和 static 修饰)
length、length()、size() 的区别
- length:数组的属性(可得到该数组的长度)
- length():String 的方法(可得到字符串的长度)
- size():集合的方法(可得到集合里元素的个数)
流程控制语句分类:1. 顺序结构;2. 选择结构(if语句、switch语句);3. 循环结构(while语句、for语句)
Forward 和 Redirect:
Forward:请求转发,服务器行为,地址栏不变。
Redirect:请求重定向(本质上为2次请求),客户端行为,地址栏改变。
Java语言中几种数组复制方法效率:System.arraycopy > clone > Arrays.copyOf > for循环
javac.exe:编译.java文件
java.exe:执行编译好的.class文件
javadoc.exe:生成Java说明文档
jdb.exe:Java调试器
javaprof.exe:剖析工具
Java 异常
Throwable: Java异常的顶级类,所有的异常都继承于这个类。Error,Exception是异常类的两个大分类。
Error: 编译或者系统性的错误(非程序异常,如OutOfMemorry)
Exception: 程序异常类,由程序内部产生(Exception又分为运行时异常、非运行时异常)。
- 运行时异常: 当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过,运行时异常可处理或者不处理。运行时异常一般常出来定义系统的自定义异常,业务根据自定义异常做出不同的处理。
- 非运行时异常: 是程序必须进行处理的异常,捕获或者抛出,如果不处理程序就不能编译通过。
Java 关键字
static:用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;用来修饰成员方法,将其变为类方法,可以直接使用 “类名.方法名” 的方式调用,常用于工具类;静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;静态导包用法,将类的方法直接导入到当前类中,从而直接使用 “方法名” 即可调用类方法,更加方便。
super:指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
this:指向对象本身的一个指针(调用属性、调用方法、利用this表示当前对象)。
final:一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在new一个对象时初始化(即只能在声明变量或构造器或代码块内初始化),而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能覆盖(重写)。变量的引用(也就是指向的地址)不可变,但是引用的内容可以变(地址中的内容可变)。
finally:用于异常处理,用来声明一个肯定会被执行的语句块。
abstract:抽象的,修饰类、方法。
抽象类(抽象类存在的意义是用来被继承的。一个类继承了一个抽象类,必须实现抽象类里面所有的抽象方法,否则,此类也是抽象类。): 不能手动创建对象(JVM可以创建抽象类的对象),但是可以声明抽象类型的引用。
抽象方法:有方法名的定义,没有实现,抽象方法的好处是允许方法的定义和实现分开。
抽象类和抽象方法的关系:含有抽象方法的类一定是抽象类,抽象类里不一定含有抽象方法。
transient:让某些被修饰的成员属性变量不被序列化。
instanceof:用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
synchronized:修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,它提供了一种锁的机制,能够确保共享变量的互斥访问,从而防止数据不一致问题的出现。
volatile:保证了不同线程间对共享变量操作时的可见性(当一个线程修改volatile修饰的变量,JVM会向处理器发送一条 lock 前缀的指令,每次数据变化,值都会强制刷入主存,另外一个线程会立即看到最新的值),禁止对指令进行重排序操作(在JDK中,Java语言为了维持顺序内部的顺序化语义,也就是为了保证程序的最终运行结果需要和在单线程严格意义的顺序化环境下执行的结果一致,程序指令的执行顺序有可能和代码的顺序不一致,这个过程就称之为指令的重排序。指令重排序的意义在于:JVM能根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能)。
Java 集合
Iterable:迭代器接口,这是Collection类的父接口。实现这个Iterable接口的对象允许使用foreach进行遍历(所有的Collection集合对象都具有 foreach可遍历性)。这个Iterable接口只有一个 iterator() 方法。它返回一个代表当前集合对象的泛型<T>迭代器(用于之后的遍历操作)
Collection:基本的集合接口,一个Collection代表一组Object的集合,这些Object被称作Collection的元素。
Set:继承自Collection (无序,不重复的集合,实现类线程不安全 解决:Set set = Collections.synchronizedSet(obj))
- HashSet:基于 HashMap 实现的,底层采用 HashMap 来保存元素,因此具有良好的存取和查找性能。当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。
- LinkedHashSet:不可以重复,有序的集合。底层采用 链表 和 哈希表的算法,链表保证元素的添加顺序,哈希表保证元素的唯一性。底层根据元素的hashCode值来决定元素的存储位置,但和HashSet不同的是,它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时(遍历)将有很好的性能(链表很适合进行遍历)。
- SortedSet:此接口主要用于排序操作,即实现此接口的子类都属于排序的子类。
- TreeSet:TreeSet是SortedSet接口的实现类(有序,不重复,底层使用 红黑树算法,擅长于范围查询),TreeSet可以确保集合元素处于排序状态(添加到TreeSet 的元素必须是可排序的)。
List:继承自Collection (有序,可重复的集合)
- ArrayList:底层数据结构是数组,查询快,增删慢(线程不安全,效率高)
- LinkedList:底层数据结构是链表,查询慢,增删快(线程不安全,效率高)
- Vector:底层数据结构是数组,查询快,增删慢(线程安全,效率低)
Queue:用于模拟"队列"这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。
- PriorityQueue:不是一个比较标准的队列实现,PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序。
Map:key-value 的键值对(key 不能重复,value 可以),用于保存具有"映射关系"的数据(Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 for-each 遍历)。
Hashtable:散列表(无序,线程安全 Hashtable 的函数都是同步的),它存储的内容是键值对(key-value)映射(不允许键或值为 null;初始容量11,之后每次扩充,容量变为原来的2n+1),影响性能因素与HashMap相同。
HashMap:散列表(无序,线程不安全;),它存储的内容是键值对(key-value)映射(键值均可为 null;初始容量16,之后每次扩充,容量变为原来的2倍)。有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作(JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(nlogN)提高了效率)。
- LinkedHashMap:继承自 HashMap,使用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,与key-value对的插入顺序一致(有序)。
ConcurrentHashMap: JDK1.7 时底层采用分段的数组+链表实现(线程安全,允许多个修改操作并发进行,其关键在于使用了锁分离技术),通过把整个Map分为N个Segment,可以提供相同的线程安全(使用segment来分段和管理锁,segment继承自ReentrantLock,因此ConcurrentHashMap使用ReentrantLock来保证线程安全),但是效率提升N倍,默认提升16倍(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值)。 JDK 1.8 时采用数组+单链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。
SortedMap:自动排序接口。
- TreeMap:红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有的key-value对处于有序状态。同样,TreeMap也有两种排序方式: 自然排序、定制排序。
Java 序列化
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
注意: 1. 序列化对象必须实现序列化接口;2. 序列化对象里面的属性是对象的话也要实现序列化接口;3. 类的对象序列化后,类的序列化ID不能轻易修改,不然反序列化会失败;4. 类的对象序列化后,类的属性有增加或者删除不会影响序列化,只是值会丢失;5. 如果父类序列化了,子类会继承父类的序列化,子类无需添加序列化接口;6. 如果父类没有序列化,子类序列化了,子类中的属性能正常序列化,但父类的属性会丢失,不能序列化;7. 用Java序列化的二进制字节数据只能由Java反序列化,不能被其他语言反序列化。如果要进行前后端或者不同语言之间的交互一般需要将对象转变成Json/Xml通用格式的数据,再恢复原来的对象;8. 如果某个字段不想序列化,在该字段前加上transient关键字即可。
Java IO
整个Java.io包中最重要的就是六个类(File、OutputStream、InputStream、Writer、Reader、RandomAccessFile)和一个接口(Serializable)。
Java I/O主要包括以下三个部分:
1.流式部分――IO的主体部分;
2.非流式部分――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;
3.其他类--文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
主要的类:
- File(非流式,文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
- RandomAccessFile(非流式,随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
- InputStream(字节流,二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
- OutputStream(字节流,二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
- Reader(字符流,文件格式操作):抽象类,基于字符的输入操作。
- Writer(字符流,文件格式操作):抽象类,基于字符的输出操作。
BIO:同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理(NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持)。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理(AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持)。
Java 并发
实现多线程的三种方式
1. 继承Thread类(jdk源码中,Thread类其实是实现了Runnable 接口的一个实例,继承Thread类后需要重写run 方法并通过start 方法启动线程。继承Thread类耦合性太强了,因为java只能单继承,所以不利于扩展);
2. 实现Runnable接口(重写run方法,并把Runnable实例传给Thread对象,Thread的start方法调用run方法再通过调用Runnable实例的run方法启动线程。所以如果一个类继承了另外一个父类,此时要实现多线程就不能通过继承Thread的类实现);
3. 实现Callable接口(重写call方法,并把Callable实例传给FutureTask对象,再把FutureTask对象传给Thread对象。它与Thread、Runnable最大的不同是Callable能返回一个异步处理的结果Future对象并能抛出异常,而其他两种不能)
线程的生命周期
NEW:表示刚创建的线程,还没有开始启动。
RUNNABLE: 表示线程已经触发start()方式调用,线程正式启动,线程处于运行中状态。
BLOCKED:表示线程阻塞,等待获取锁,如碰到synchronized、lock等关键字等占用临界区的情况,一旦获取到锁就进行RUNNABLE状态继续运行。
- 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态。
- 2.同步阻塞 :线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- 3.其他阻塞 :通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
WAITING:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如通过wait()方法进行等待的线程等待一个notify()或者notifyAll()方法,通过join()方法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线程就进入了RUNNABLE状态继续运行。
TIMED_WAITING:表示线程进入了一个有时限的等待,如sleep(3000),等待3秒后线程重新进行RUNNABLE状态继续运行。
TERMINATED:表示线程执行完毕后,进行终止状态。
一旦线程通过start方法启动后就再也不能回到初始NEW状态,线程终止后也不能再回到RUNNABLE状态。
注:这些状态的枚举值都定义在 java.lang.Thread.State 下。
Thread API
sleep(): 是一个静态方法(有两个重载方法 1. 需传入毫秒数;2. 既需传毫秒数也需要传纳秒数),会使当前线程进入指定的秒数休眠,暂停执行(最终按系统的定时器和调度器的精度为准)。
yield():属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源(如果CPU资源不紧张则会忽略这种提醒),调用yield方法会使当前线程从 RUNNING 状态切换到 RUNNABLE 状态。
sleep 与 yield:1. sleep 会导致当前线程暂停指定的时间,没有CPU时间片的消耗;2. yield 只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换;3. sleep 会使线程短暂 block,会在给定的时间内释放CPU资源;4. 一个线程 sleep 另一个线程调用 interrupt 会捕捉到中断信号,而 yield 不会;
setPriority() / getPriority():设置 / 获取线程优先级。
getId():获取线程的Id。
currentThread:用于返回当前执行线程的引用。
getContextClassLoader():获取线程上下文的类加载器。
setContextClassLoader():设置该线程的类加载器(这个方法可以打破Java类加载器的父委托机制)。
interrupt():线程在阻塞状态(调用 Object 的 wait() 方法;Thread 的 sleep() / join() 方法;InterruptibleChannel 的 io 操作;Selector 的 wakeup() 方法会使当前线程进入阻塞状态)时,调用 interrupt 方法就可以打断阻塞。
isInterrupted():是 Thread 的一个成员方法,它主要判断当前线程是否被打断。
锁
CAS:比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值(这是作为单个原子操作完成的)。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成。
乐观锁:认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改(一般使用 版本号机制 或 CAS操作实现)。
- version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
// SQL
update table set x=x+1, version=version+1 where id=#{id} and version=#{version}
- CAS操作方式:涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
悲观锁:每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁(synchronized的思想也是悲观锁)。
ing...
数据库事物隔离级别
READ_UNCOMMITTED(未提交读): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
READ_COMMITTED(提交读): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
REPEATABLE_READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(串行): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
注:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别。
JSP
JSP 的九个内置对象:
- request:(HttpServletRequest类中)封装客户端的请求,其中包含来自GET或POST请求的参数;
- response:(HttpServletResponse类中)封装服务器对客户端的响应;
- pageContext:(ServletContext类中)通过该对象可以获取其他对象;
- session:(HttpSessoin类中)封装用户会话的对象;
- application:(ServletContext类中)封装服务器运行环境的对象;
- out:(JspWriter类中)输出服务器响应的输出流对象;
- config:(ServletConfig类中)Web应用的配置对象;
- page:(Object类中)JSP页面本身(相当于Java程序中的this);
- exception:封装页面抛出异常的对象;
动态include和静态include
静态 include:用 include 伪码实现 , 不会检查所含文件的变化 , 适用于包含静态页面 <%@ include file="included.htm" %> 。先将文件的代码被原封不动地加入到了主页面从而合成一个文件,然后再进行翻译。
动态 include:用 jsp:include 动作实现 <jsp:include page="included.jsp" flush="true" /> 它总是会检查所含文件中的变化 , 适合用于包含动态页面 , 并且可以带参数。各个文件分别先编译,然后组合成一个文件。
JSP中四种作用域:
- page:代表与一个页面相关的对象和属性。
- request:代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
- session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- application:代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。
JSP中七大动作:
- jsp:include:在页面被请求的时候引入一个文件。
- jsp:useBean:寻找或者实例化一个 JavaBean。
- jsp:setProperty:设置 JavaBean 的属性。
- jsp:getProperty:输出某个 JavaBean 的属性。
- jsp:forward:把请求转到一个新的页面。
- jsp:plugin:根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记