java_4.0
2021-06-12 12:00:01 0 举报AI智能生成
1
后端开发
模板推荐
作者其他创作
大纲/内容
基础
JDK和JRE
JRE:java运行时环境,包含了java虚拟机,java基础类库
JDK是java开发工具包,包括JRE
跨平台原理
Java先编译后解释,同一个.class文件在不同的虚拟机会
得到不同的机器指令(Windows和Linux的机器指令不同),
但是最终执行的结果却是相同的
JAVA虚拟机的作用
解释运行字节码程序(Java程
序编译后产生)消除平台差异性
数据类型
8种基本数据类型
byte
1字节
[-2e7,2e7-1]
short
2字节
char
2字节
int
4字节
long
8字节
float
4字节
double
8字节
boolean
1字节
装箱和拆箱
装箱:int->integer
拆箱:integer->int
自动类型转换
转换原则:从低精度向高精度转换byte->(short、char)->int->long->float->double
两个char型运算时,自动转换为int型;
当char与别的类型运算时,也会先自动转换为int型的,
再做其它类型的自动转换
在Java中,一个空Object对象的大小是8byte
这个大小只是保存堆中一个没有任何属性的对象的大小
Object ob = new Object();所占的空间为:4byte+8byte,
4byte是上面部分所说的Java栈中保存引用的所需要的空间
8byte则是Java堆中对象的信息,所有的Java非基本类型的
对象都需要默认继承Object对象,因此不论什么样的Java对象,
其大小都必须是大于8byte
java 中的 Math.round(-1.5) 等于多少
+0.5再做四舍五入
java特性
封装
是隐藏对象的属性和实现细节,仅对外提供公共访问方式
继承
四种访问权限
Public:对所有类可见
Protected:对同一包中的类,和子类可见
Private:仅对类本身可见
Default:对同一包中的类可见
多态
编译时多态通过方法重载
重载只能通过不同的方法参数区分
参数个数不同
参数类型不同
通过指向父类的指针,来调用在不同子类中实现的方法
运行时多态通过方法重写
多态用到了动态绑定
编译时类型与运行时类型不一致就会发生运行时动态绑定(典型的就是重载)
向上转型与向下转型
上转型:是子类对象由父类引用,格式:parent p=new son
下转型:是父类向下强制转换到子类对象
*前提是该父类对象必须是经过上转型的对象
//对进行过上转型的对象,进行强制下转型
21 Son s=(Son)p;
java元注解
能注解到注解上的注解,能用在其他注解上的注解
自定义注解:https://www.cnblogs.com/yanze/p/9296237.html
java注解
注解的本质就是一个继承了 Annotation 接口的接口
解析一个类或者方法
的注解往往有两种形式
一种是编译期直接的扫描
一种是运行期反射
==和equals
一个Integer只有在与 int比较,会先将Integer转换成int类型,
再做值比较,所以返回的是true,integer 类型变量缓存-128到127
因此判断Integer a=num与Integer b=num是否相等时,主要看num
在不在缓存范围内,-128<=num<=127则相等,否则不相等
==
基本类型:比较的是值是否相同
引用类型:比较的是引用是否相同
equals
equals 本质上就是 ==,但重写了string和integer,把引用比较改成了值比较
equals和hashcode有什么关系
先判断hashcode是否相等,再判断是否equals
String StringBuffer StringBuilder
String为啥不可变?
String类源码中是一个value的字符数组,
被private final修饰,被final修饰所以就不能变了
String为什么设计成final不可变
不可变性支持线程安全
不可变性支持字符串常量池
节省内存空间
String类是不可以被继承的;
被final修饰的类是不可以被继承的
stringBuffer是线程安全
String s = new String(“abc”);
创建了2个String对象和一个引用。
一个在常量池的“abc”常量,还有一个对象是new
出来的在堆里;s则是指向该对象的引用
String+是StringBuffer的append()方法来实现的
抽象类和接口区别
一个类只可以继承一个抽象类,但可以实现多个接口
抽象类可以有构造方法,接口中不能有构造方法
抽象类可以有普通的成员变量,接口中不能有普通的成员变量
抽象类中可以包含静态方法,接口中不能包含静态方法
静态方法与非静态方法的区别
与静态成员变量一样,属于类本身,例化前即可使用,在类装载的时候被装载到
内存(Memory),不自动进行销毁,会一直存在于内存中,直到JVM关闭
属于实例对象,实例化后才会分配内存,必须通过类的实例来引用,
不会常驻内存,当实例对象被JVM 回收之后,也跟着消失
静态方法的使用效率比非静态方法的效率高;
因为静态方法没有反复的分配、回收内存
非静态方法可以访问类中的任何成员,静态方法只能访问类中的静态成员
泛型和类型擦除
参数化类型
写一个泛型类与普通类的区别是在类名后面加个<T>
解释一下extends
和super 泛型限定符
extends上限通配符,用来限制类型的上限,只能传入本类
和子类,add方法受阻,可以从一个数据类型里获取数据;
List<? extends Number>eList存放Number及其子类的对象
List<? extends Number>eList不能够确定实例化
对象的具体类型,因此无法add具体对象至列表中
super 指定下界限,只能传入本类和父类
List<? super Integer> 无法确定sList中存放的对象的具体类型,因此sList.get获取的值存在不确定性
子类对象的引用可以赋值给父类对象的引用,因此下界类型通配符
get方法受限,但可以往列表中添加各种数据类型的对象
反射
运用反射的原理创建对象
newInstance()方法,获取class
类型之后,可以创建该类型的对象
Person c3 = (Person)c2.newInstance();
Class.forName 静态方法
class c2 = Class.forName("test.Person");
使用类对象的 getClass() 方法
Class c1 = person.getClass();
通过类对象的getConstructor()或
getDeclaredConstructor()方法获
得构造器(Constructor)对象并
调用其newInstance()方法创建对象
private修饰的方法与变量能否反射(不能)
Java 反射中,Class.forName
和 ClassLoader 的区别
classloader值是将类的.class文件
加载到jvm中,不会执行static代码块
Spring框架中的IOC的实现就是使用的ClassLoader
class.forName不但将类的.class文件
加载到jvm中,还会执行static代码块
JDBC用Class.forName()方法来加载数据库连接驱动,因为在JDBC规范中
明确要求Driver(数据库驱动)类必须向DriverManager注册自己。
classloader
类加载器分类
启动类加载器
加载 Java 的核心库
扩展类加载器
加载 Java 的扩展库
应用程序类加载器
加载Java 应用的类
作用
在运行时创建对象
获取任意一个类所具有的成员变量和方法
判断一个对象所属的类
调用一个对象的任意方法
序列化
对象中被static或transient修饰的变量,在序列化时不被保存
动态代理
加事务
加日志
加权限
spring AOP
可以对proxy代理类(中间类)添加一些额外的方法
克隆
浅克隆
浅克隆不会克隆原对象中的引用类型,仅仅拷贝了引用类型的指向
深克隆
深克隆的实现就是在引用类型所在的类实现Cloneable接口,
并使用public访问修饰符重写clone方法。
关键字
throw和throws
throws在方法后边声明一个方法可能产生的
所有异常,不做任何处理而是将异常往上传
throw是语句抛出一个异常, 用在方法体内,
跟的是异常对象名,由方法体内的语句处理
final、finally、finalize
final
可以用来修饰类,方法和变量(成员变量或局部变量)
用final修饰类的时,表明该类不能被其他类所继承
修饰方法,以防止继承类对其进行更改
修饰变量,final成员变量表示常量,只能被赋值一次,赋值后其值不再改变
finally
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块
只有与finally对应的try语句块得到执行的情况下,finally语句块才可能会执行
finally用法特殊,所以会撤销之前的return语句,继续执行最后的finally块中的代码
finalize
在java.lang.Object里定义的,也就是说每一个对象都有
这么个方法,这个方法在gc启动,该对象被回收的时候被调用
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着
gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了
try、catch、finally
在执行try、catch中的return之前一定会执行finally中的代码(如果finally存在),
如果finally中有return语句,就会直接执行finally中的return方法,所以finally
中的return语句一定会被执行的。编译器把finally中的return语句标识为一个warning.
Exception和Error
异常
Exception是java程序运行中可预料的异常情况,
可以获取到这种异常,并且对这种异常进行业务外的处理
runtimeException
出现这类异常的时候程序会由虚拟机接管
indexOutOfBoundsException、IllegaArguementException、NullPointerException
checkedException
主要是指IO异常、SQL异常等。对于这种异常,
JVM要求我们必须对其进行cathc处理
SQLException 、IOexception 、 FileNotFoundException
错误
Error是java程序运行中不可预料的异常情况,这种异常
发生以后,会直接导致JVM不可处理或者不可恢复
比如OutOfMemoryError、NoClassDefFoundError
switch支持的数据类型(java8)
byte、short、int 、char及其包装类型和string以及枚举型
DOM和SAX解析xml的不同
dom是基于内存的,把xml读到内存中,消耗很大的内存空间;
SAX是基于事件驱动的方式,事件被触发时,获取相应的xml数据解析
dom可以向xml插入数据,SAX不可以
dom可以随机访问,SAX不可以
Java包装类的缓存
具体的实现方式为在类中预先创建频繁使用的包装类对象,
当需要使用某个包装类的对象时,如果该对象封装的值
在缓存的范围内,就返回缓存的对象,否则创建新的对象并返回
哪些包装类没有缓存
float、double
IO
IO模型
同步阻塞IO(BIO)
BIO是一个连接一个线程
同步非阻塞IO(NIO)
原理
缓冲区buffer
通道channel
选择器select、poll、epoll
处理器的嗅探技术
通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期
select和poll
select和poll具有O(n)的无差别轮询复杂度
poll是一个系统调用
select和poll的fd_set集合,是在用户态进行定义,系统调用时,
将这个参数传入到内核态中,这是一次复制,这个数据结构就在
内核态也被复制一份;(而select和poll的系统调用结束,发现
有一些fd有事件来了,再将这个数据结构,从内核态传回用户态,
然后用户再进行遍历;这就是两次fd数组的复制
select支持的文件描述符数量太小,默认是1024
epoll
epoll事件复杂度为O(1),基于事件驱动的
epoll由epoll_create/epoll_ctl/epoll_wait三个系统调用组成
epoll改进在于通过epoll_create系统调用,直接在内核态
创建fd数组,没复制,epoll系统调用结束,发现有一些fd
事件来了,将内核态传入用户态,这有一次复制
epoll不需要每次都完整读入输出ufds(文件描述符数组),
只需使用epoll_ctl调整其中一小部分,不需要每次
epoll_wait都执行一次加入删除等待队列操作
子主题
工作过程
首先:nio主要有几个事件,包括读就绪,写就绪, 新连接
到来, 当有新事件操作时,首先把事件注册到对应的处理器
其次:并由一个线程不断循环等待,调用操作系统底层的函数select()
或者 epoll(Windows是iocp),并负责向操作系统查询IO是否就绪
(标记:从网卡已经拷贝到内核缓存区,准备就绪),如果就绪执行事件处
理器(从内核缓存区到用户内存);
这个过程就是利用了Reactor事件驱动的模式
epoll的两种工作模式
ET模式
仅当状态发生变化时才会通知
LT模式
只要还有没有处理的事件就会一直通知
NIO是一个请求一个线程
适用场景:聊天服务器
异步非阻塞IO(AIO)
AIO是一个有效请求一个线程
适用场景:相册服务器
io流
字节流
按字节读,可用于文件、图片、视频、音频
字节流继承inputStream和OutputStream
字符流
按照字符读,一般用于文件
字符流继承自Reader和Writer
对于过 IO 在内存中频繁处理字符串的情况使用
字符流会好些,因为字符流具备缓冲区,提高了性能
什么是缓冲区?有什么作用
缓冲区就是一段特殊的内存区域,很多情况下当程序需要
频繁地操作一个资源(如文件或数据库)则性能会很低,
所以为了提升性能就可以将一部分数据暂时读写到缓存区,
以后直接从此区域中读写数据即可,这样就显著提升了性
对于 Java 字符流的操作都是在缓冲区操作的,
所以如果我们想在字符流操作中主动将缓冲区
刷新到文件则可以使用 flush() 方法操作
字符流和字节流有什么区别
字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的
字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件
两个对称性
输入-输出对称
比如InputStream和OutputStream各自占据Byte
流的输入和输出的两个平行的等级结构的根部
byte-char对称
比如Reader和Writer各自占据Char流的输入
和输出的两个平行的等级结构的根部
两设计模式
装饰者
InputStream
FileInputStream
处理文件
ByteArrayInputStream
处理字节数组
FilerInputStream
BufferedInputStream
PushbackInputStream
DatainputStream
PipeInputStream
处理线程间的输入流
ObjectInputStream
处理被实例化的对象
适配器
StringBufferInputStream是一个适配器类,StringBufferInputStream
继承了InputStream类型,持有一个对String类型的引用。
这是将String对象适配成InputStream类型的对象形式的适配器模式
#字节码的结构
魔数
作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件
主版本号/副版本号
高版本的JDK兼容以前版本的Class文件,但不能运行以后版本
的class文件,即使格式没变,虚拟机也拒绝执行
常量池
Java类中定义的很多信息都是由常量池维护和描述的。可以将常量池看作是
Class文件的资源库。比如:Java类中定义的方法与变量信息,都是存储在常量
池中。常量池中主要存储两类常量:字面常量和符号引用。字面量,如文本字符
串,Java中声明为常量值,而符号引用如类和接口的全局限定名,字段的名称和
描述符,方法的名称和描述符等
类索引、父类索引、接口索引
Class文件中由这三项数据来确定这个类的继承关系
类索引用于确定这个类的全限定名
父类索引用于确定这个类的父类的全限定名
接口索引集合就用来描述哪个类实现了哪些接口
访问标志
用于识别一些类或者接口层次的访问信息,包括:这个Class
是类还是接口、是否定义为public类型、是否定义为abstract类型等
字段表集合
用于描述接口或者类中声明的变量
方法集合
描述该类中的方法
方法计数器
表示类中总有有几个方法
Enum类
初步理解
枚举类型是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型
多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性
枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器
也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的
枚举实现原理
使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在
的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,同
时编译器会为该类创建两个方法,分别是values()和valueOf()
values()方法的作用就是获取枚举类中的所有变量,并作为数组返回
valueOf方法的作用类似根据名称获取枚举变量
枚举的使用
通常用来表示诸如颜色、方式、类别、状态等等
数目有限、形式离散、表达又极为明确的量
enum类中确实可以像定义常规类一样声明变量或者成员方法,务必在声明完
枚举实例后使用分号分开,倘若在枚举实例前定义任何方法,编译器都将会报错
枚举类型的实例终究不能作为类型传递使用
enum类并不能再继承其它类,可以实现实现接口
枚举与switch
枚举型被switch所支持
EnumMap
EnumMap要求其Key必须为Enum类型
效率高,因为其内部是通过数组实现
EnumMap的key值不能为null
EnumMap保证Key顺序与枚举中的顺序一致
EnumSet
所有元素都必须是枚举类型
内部实现是位向量
不允许使用 null 元素
创建EnumSet并不能使用new关键字,因为它是个抽象类,而应该使用其提供的静态工厂方法
为什么构造函数必须是私有的
保证每一个枚举类元素的唯一实例,
是不会允许外部进行new的
枚举是一个被命名的整型常数的集合(byte、Int32 或 UInt64)
内部类
优点
能够非常好的解决多重继承的问题
内部类提供了更好的封装,除了该外围类,其他类都不能访问
在单个外围类中,可以让多个内部类以不同
的方式实现同一个接口,或者继承同一个类
内部类分类
成员内部类
内部类定义在外部类的内部,相当于外部类的一个成员变量的位置,
内部类可以使用任意访问控制符,如public、protected、private等
内部类中定义的 show() 方法可以直接访问外部类中的数据,而不受访问控制符的影响
定义了成员内部类后,必须使用外部类对象来创建内部类对象,不能直接去 new
一个内部类对象,即:内部类 对象名 = 外部类对象.new 内部类( )
编译含有内部类的程序后,会发现产生了两个 .class 文件: Outer.class,Outer$Inner.class{}
成员内部类中不能存在任何 static 的变量和方法,可以定义常量
静态内部类
静态内部类不能直接访问外部类的非静态成员,
但可以通过 new 外部类().成员 的方式访问
如果外部类的静态成员与内部类的成员名称相同,
可通过“类名.静态成员”访问外部类的静态成员
如果外部类的静态成员与内部类的成员名称不相同,
则可通过“成员名”直接调用外部类的静态成员
创建静态内部类的对象时,不需要外部类的对象,
可以直接创建 内部类 对象名 = new 内部类();
方法内部类
其作用域仅限于方法内,方法外部无法访问该内部类
像方法里面的一个局部变量一样,不能修饰符
只能访问方法中定义的 final 类型的局部变量,在JDK8版本之中,
方法内部类中调用方法中的局部变量,可以不需要修饰为final
匿名内部类
匿名内部类是直接使用 new 来生成一个对象的引用
匿名内部类仅能被使用一次,创建匿名内部类时会立即
创建一个该类的实例但该实例不能够被重复使用
使用匿名内部类时,我们必须是继承一个类或者实现一个接口,
但是两者不可兼得,同时也只能继承一个类或者实现一个接口
匿名内部类中是不能定义构造函数的,匿名内部
类中不能存在任何的静态成员变量和静态方法
匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法
匿名内部类初始化:使用构造代码块,利用构造
代码块能够达到为匿名内部类创建一个构造器的效果
只能访问方法中定义的 final 类型的局部变量,在JDK8版本之中,
方法内部类中调用方法中的局部变量,可以不需要修饰为final
匿名内部类是否会编译成class文件
匿名内部类编译后会生成class文件,名称是 1、2...数字
sort排序原理
使用了两种排序方法,快速排序和优化的归并排序
快速排序主要是对那些基本类型数据排序,
而归并排序用于对Object类型进行排序。
快速排序是不稳定的,而归并排序是稳定的
对于基本数据类型,稳定性没有意义,而
对于Object类型,稳定性是比较重要的
面向接口编程的好处
接口是定义(规范,约束)与实现的分离
接口本质上就是由制定者来协调实现者和调用者之间的关系
设计模式的原则里的开闭原则,
其实就是要使用接口来实现对扩展开放,对修改关闭
object类的方法
clone()
创建并返回此对象的一个副本
equals(Object obj)
指示其他某个对象是否与此对象“相等”
getClass()
返回此 Object 的运行时类
hashCode()
返回该对象的哈希码值
notify()
唤醒在此对象监视器上等待的单个线程
notifyAll()
唤醒在此对象监视器上等待的所有线程
tostring()
返回该对象的字符串表示
wait()
在其他线程调用此对象的 notify() 方法
或 notifyAll() 方法前,导致当前线程等待
Comparable和Comparator接口
是干什么的?列出它们的区别
使用Comparable方式比较时,将比较的规则写入了比较的类型中,
其特点是高内聚;规则需要修改就必须修改这个类型的源代码;
使用Comparator方式比较,其特点是易维护,需要自定义一个比较器,
后续比较规则的修改,仅仅是改这个比较器中的代码即可
Collection和Collections的区别
Collection 是一个集合接口
提供了对集合对象进行基本操作的通用接口方法
Collections 是一个包装类
提供了一系列静态方法,用于对集合中元素进行排序
如何理解Java多线程回调方法?
回调 就是客户程序C调用服务程序S中的某个方法A
然后S又在某个时候反过来调用C中的某个方法B
对于C来说 这个B便叫做回调方法
System.arraycopy()
对于一维数组来说,这种复制属性值
传递,修改副本不会影响原来的值
对于二维或者一维数组中存放的是对象时,
复制结果是一维的引用变量传递给副本的一
维数组,修改副本时,会影响原来的数组
Fork/Join
用于并行执行任务的框架,把大任务分割成若干个小任务,
汇总每个小任务结果后得到大任务结果的框架
容器
List
ArrayList
默认大小是10,每次1.5倍
stack
vector
LinkedList
Set
hashset
treeset
TreeSet可以确保集合元素处于排序状态
map
HashMap
初始容量为16,扩容每次乘以2
装载因子默认0.75
threshold是容量乘以装载因子
数组长度保持2的次幂,length-1的低位都为1,
会使得获得的数组索引index更加均匀
HashMap什么时候会进行扩容
1.7的源码
执行put方法的
if ((size >= threshold) && (null != table[bucketIndex])) {}
根据key的hash值,对数组的大小取模 hash & (length-1)
1.8的源码
putVal之后执行if (++size > threshold){}
扩容时不需要重新计算hash,关键看
原来的hash值新增的那个bit是1还是0
是0的话索引没变,是1的话索引变成“原索引+oldCap”
HashMap什么时候会进行rehash
扩容的过程中需要进行ReHash
HashMap线程不安全的体现
扩容造成死循环
https://www.jianshu.com/p/1e9cf0ac07f4
当在数组该位置get寻找对应的key时,就发生了死循环
put引发扩容,浪费内存
扩容时,删除不了
某个线程在删除某个元素时,发现删除不了,
因为table指向了新数组,且新数组还没有数据
2个同时put,导致1个丢失,被后1个put给覆盖掉了
在扩容的时候插入数据,
有可能会把新插入的覆盖住
在扩容时,在数据从旧数组复制到新数组过程中,
插入一条数据到新数组中,但数据复制过程中,
HashMap是没有检查新数组上的位置是否为空,
新插入的数据会被后面从旧数组中复制过来的数据覆盖住
JDK1.8中HashMap的性能优化
HashMap在jdk1.8之后引入了红黑树的概念,
表示若桶中链表元素超过8时,会自动转化成红黑树;
若桶中元素小于等于6时,树结构还原成链表形式。
选择6和8的原因
中间有个差值7可以防止链表和树之间频繁的转换
HashMap1.7为什么使用的是头插法,
1.8后使用尾插法,这个改变有什么作用吗
JDK1.8之后是因为加入了红黑树使用尾插法,
能够避免出现逆序且链表死循环的问题
扩容时不需要重新计算hash,关键看
原来的hash值新增的那个bit是1还是0
是0的话索引没变,是1的话索引变成“原索引+oldCap”
Jdk1.8中没有indexFor函数,直接使用
table[index = (n – 1) & hash]
(与运算交换左右,结果不变
TreeMap
适用于按自然顺序或自定义顺序遍历键(key)
TreeMap 的底层就是一颗红黑树
只能在单线程下安全使用
与hashmap的不同
HashMap的结果是没有排序的,
而TreeMap输出的结果是排好序的
ConcurrentSkipListMap
底层是通过跳表来实现的
适用于按自然顺序或自定义顺序遍历键(key)
多线程安全
HashTable
特点:锁住整个容器
与hashmap的不同
HashMap的key、value都可以为null
Hashtable的key、value都不可以为null
HashMap的函数则是非同步的,非线程安全的
hashTable是线程安全的
HashMap只支持Iterator(迭代器)遍历
Hashtable支持Iterator(迭代器)和
Enumeration(枚举器)两种方式遍历
Hashtable默认的“加载因子”是0.75, 默认的容量大小是11
初始size为11,扩容:newsize = olesize*2+1
WeakHashMap
与hashmap的不同
HashMap的“键”是强引用,
WeakHashMap的键是弱引用
LinkedHashMap
与hashmap的不同
比 HashMap 多维护了一个双向链表
可以按照插入的顺序从头部或者从尾部迭代
内存相比而言要比 HashMap 大
ConcurrentHashMap
锁分段技术来保证线程安全的
1.7与1.8的区别
1.8使用Unsafe类的CAS自旋赋值+synchronized同步+
LockSupport阻塞等手段实现的高效并发
采用头插法
与hashmap的不同
Iterator和ListIterator
ListIterator有add()方法,可以向List中添加对象,而Iterator不能
iterator()方法在set和list接口中都有定义,ListIterator()仅存在于list接口中(或实现类中)
ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历
ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能
queue
PriorityQueue
fail-fast机制
集合结构上发生了改变(.remove(i))该机制将尽最大努力抛出异常
jvm
组成及其作用
类加载器
类加载过程
加载:根据查找路径找到相应的 class 文件然后导入
检查:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程;
符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作
双亲委派
工作原理
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类
的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请
求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘
若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
优势
避免类的重复加载
类加载器分类
启动类加载器
加载 Java 的核心库
扩展类加载器
加载 Java 的扩展库
应用程序类加载器
加载Java 应用的类
执行引擎
本地库接口
运行时数据区
程序计数器
java虚拟机栈
本地方法栈
堆(年轻代:老年代=1:2)
年轻代(8:1:1)
Eden区
from Survivor
to Survivor
老年代
方法区(jdk1.8没了方法区,元数据空间)
线程共享的
堆和方法区
jvm内存(自己总结的简要版)
堆
实例
方法区
加载的类信息
常量池
各种字面量
符号引用
静态变量
栈
局部变量
包括基本类型和引用类型
方法
程序计数器
本地方法区
直接内存
分支主题
对象的访问方式
句柄方式
堆中有一个句柄池,分别指向堆中的实例池和方法区中的对象类型数据
分支主题
指针方式
堆中直接存储对象实例数据,对象类型指针指向方法区
分支主题
和句柄方式比,少了一次指针访问
引用类型
强
A a=new A()
软
数据库查询缓存
弱
WeakHashMap、ThreadLocalMap
虚
一个对象的引用有多个,该如何判断它的可达性
单弱多强
判断一个对象
是否可被回收
引用计数
可达性分析(哪些可作为GC root)
虚拟机栈中引用的对象
本地方法栈中引用的对象
静态成员变量或者常量引用的对象
垃圾收集
垃圾收集器算法
复制
标记-整理
标记-清除
分代算法
垃圾收集器
老年代回收器
CMS
牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器
四个大致步骤
初始标记
仅仅只是标记一下GC Roots能直接关联到的对象
stop of world)
并发标记
进行GC Roots Tracing
重新标记
为了修正并发标记期间因用户程序继续运作而
导致标记产生变动的那一部分对象的标记记录
stop of world)
并发清理
使用的是标记-清除的算法实现的
Serial old
Parallen old
新生代回收器
serial
Parnew
Parallel Scavenge
整堆回收器
G1
一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项
四个大致步骤
初始标记
并发标记
最终标记
stop of world):
筛选回收
G1从整体来看是基于“标记整理”算法实现的收集器;
从局部上来看是基于“复制”算法实现的
最大的特点是引入分区的思路,弱化了分代的概念
每个分区被标记了E、S、O和H,H表示这些Region存储的是巨型对象,
新建对象大小超过Region大小一半时,直接在新的一个或多个连续分区中分配,
并标记为H
JVM的栈上分配与逃逸分析(Escape Analysis)
原本分配到堆上的对象分配到栈上
内存分配与回收策略
Minor gc
Major gc
Full gc
如何减少gc的次数
尽量少用静态变量
对象不用时最好显式置为null
增大堆的最大值设置
尽量使用stringBuffer而不是string,减少不必要的中间对象
经常使用的图片可以使用软引用类型
内存泄漏(无法释放已申请的内存空间)
什么条件下会造成内存泄漏
对象可达但是无用
内存泄漏的场景
循环创建对象
各种连接没有及时释放资源
使用Jconsole查找内存泄漏
内存溢出(没有足够的内存供申请者使用)
虚拟机和本地方法栈溢出
堆溢出
方法区溢出
内存映像分析工具(eclipse)
对dump出来的堆转存快照
调优工具
jinfo
查看java进程的运行时jvm参数详细信息,
例如最大堆内存、使用的什么垃圾收集器等
jps
先要使用jps查看出当前有哪些Java进程,
获取该Java进程的id后再对该进程进行处理。
jmap(查看内存)
jmap -dump:live, format=b, file=fileName pid
用于生成堆转储快照文件(某一时刻的);
将内存使用情况导出到文件中,
再用jhat、MAT、VisualVM分析查看,
以便查找内存溢出原因
jmap -heap pid
查看heap的概要信息,GC使用的算法、
heap的配置及wise heap的使用情况.
jmap -histo[:live] pid
查看堆内存中的每个类的类名、实例数量、内存占用大小
Jstat(查看性能)
查看classloader,compiler,gc相关信息,
实时监控资源和性能 。jstat工具特别强大,
可以用来监视VM内存内的各种堆和非堆的大小及其内存使用量。
jhat
用于分析生成堆转储快照,一般结合jmap使用
jstack
jstack用于生成java虚拟机当前时刻的线程快照,
主要目的是定位线程出现长时间停顿的原因,
如线程间死锁、死循环、请求外部资源导致的长时间等待等。
jstack应用一: JVM调优之jstack找出最耗cpu的线程并定位代码
通过jvm 查看死锁
jstack -l jvm_pid
jconsole
内存监控和线程监控
提供 Java 某个进程的内存、线程、类加载、
jvm 概要以及 MBean 等的实时信息
检测线程死锁使用JDK给我们的的工具JConsole
VisualVM
集成了多个 JDK 命令行工具的可视化工具,
它能为您提供强大的分析能力
对 Java 应用程序做性能分析和调优。
这些功能包括生成和分析海量数据、跟踪内存泄漏、
监控垃圾回收器、执行内存和 CPU 分析,同时它还
支持在 MBeans 上进行浏览和操作
第三方调优工具
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具
GChisto
分析gc日志的工具,可以通过gc日志来分析:
Minor GC、full gc的时间、频率等等,
通过列表、报表、图表等不同的形式来反应gc的情况
17 个 JVM 参数
verbose:gc
启动jvm的时候,输出jvm里面的gc信息
-XX:+printGC
打印的GC信息
-XX:+PrintGCDetails
打印GC的详细信息
-XX:+PrintGCTimeStamps
打印GC发生的时间戳
-X:loggc:log/gc.log
指定输出gc.log的文件位置
-XX:+PrintHeapAtGC
表示每次GC后,都打印堆的信息
-XX:+TraceClassLoading
监控类的加载
-XX:+PrintClassHistogram
跟踪参数
-Xmx -Xms
这个就表示设置堆内存的最大值和最小值
-Xmn
设置新生代的内存大小。
-XX:NewRatio
新生代和老年代的比例。比如:1:4,就是新生代占五分之一。
-XX:SurvivorRatio
设置两个Survivor区和eden区的比例。
比如:2:8 ,就是一个Survivor区占十分之一。
XX:+HeapDumpOnOutMemoryError
发生OOM时,导出堆的信息到文件。
-XX:+HeapDumpPath
表示,导出堆信息的文件路径。
-XX:OnOutOfMemoryError<br>
当系统产生OOM时,执行一个指定的脚本
,这个脚本可以是任意功能的。
比如生成当前线程的dump文件,
或者是发送邮件和重启系统。
-XX:PermSize -XX:MaxPermSize
设置永久区的内存大小和最大值;
永久区内存用光也会导致OOM的发生。
-Xss
设置栈的大小。栈都是每个线程独有一个,
所有一般都是几百k的大小。
new的对象如何不分配在堆而分配在栈上
方法逃逸
并发
进程
进程同步机制
共享内存
套接字
消息队列
信号
用于通知接收进程某个事件已经发生
信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问
管道
一种半双工的通信方式,数据只能单向流动,
而且只能在具有亲缘关系的进程间使用。
进程的亲缘关系通常是指父子进程关系
命名管道
有名管道也是半双工的通信方式,
它允许无亲缘关系进程间的通信
进程的状态和转换
分支主题
守护进程
守护进程就是在后台运行,不与任何终端关联的进程,
通常情况下守护进程在系统启动时就在运行,
它们以root用户或者其他特殊用户(apache和
postfix)运行,并能处理一些系统级的任务.
孤儿进程
如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程
僵尸进程
一个进程已经终止了,但是其父进程还没有获取其状态
如何避免
通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。
如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);
表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的
fork两次,父进程fork一个子进程,然后继续工作,
子进程fork一个孙进程后退出,那么孙进程被init接管,
孙进程结束后,init会回收。不过子进程的回收还要自己做。
父进程通过wait和waitpid等函数等待子进程结束,
这会导致父进程挂起。
如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出
线程
创建线程的方式
四种创建线程方式
runnable
callable
future
future task
Callable接口的任务线程能返回执行结果
Thread
使用线程池例如用Executor框架
一个类可以同时继承Thread和实现Runnable接口
实现Callable接口的任务线程能返回执行结果;
而实现Runnable接口的任务线程不能返回结果
future的底层实现异步原理
提供了三种功能
判断任务是否完成
能够中断任务<br>
能够获取任务执行结果
future的底层实现异步原理
在客户端请求的时候,直接返回客户端需要的数据
(此数据不一定完整,只是简单的一点不耗时的操作),
但是客户端并不一定马上使用所有的信息,此时就有了
时间去完善客户需要的信息
与FutureTask的区别和联系
future是个接口,futuretask可以通过实现该接口,
调用get方法返回执行结果
守护线程
守护线程通常执行一些任务,当所有非守护线程终止的时候,J
VM简单的丢弃掉所有现存的守护线程.一旦其它非守护线程执
行完,不一定所有的守护线程都会执行完才退出,它们可能在非
守护线程执行完后的某个时刻退出
使用场景
来为其它线程提供服务支持.
线程的状态
分支主题
线程调度器
负责为runnable状态线程分配cpu时间
线程同步机制
临界区
指的是一个访问共用资源(例如:共用设备或是共用存储器)
的程序片段,而这些共用资源又无法同时被多个线程访问的特性
互斥量
锁
信号量
volatile
事件
选择器epoll()
线
程
池
管理一组工作线程
等待执行任务的消息队列
创
建
线
程
池
的
方
式
创
建
线
程
池
newSingleThreadExecutor
(单线程线程池)
适用场景
适用于串行执行任务的场景
newFixedThreadPool
(固定大小线程池)
特点
coresize和maxsize相同
队列用的LinkedBlockingQueue无界
keepAliveTime为0
工作机制
1.线程数少于核心线程数,新建线程执行任务
2.线程数等于核心线程数后,将任务加入阻塞队列
3.执行完任务的线程反复去队列中取任务执行
适用场景
适用于处理CPU密集型的任务,
确保CPU在长期被工作线程使用的情况下,
尽可能的少的分配线程即可。一般Ncpu+1
newCachedThreadPool
(可缓存线程的线程池)
特点
核心线程数为0,且最大线程数为Integer.MAX_VALUE
阻塞队列是SynchronousQueue
keepAliveTime为60s
工作机制
1.没有核心线程,直接向SynchronousQueue中提交任务
2如果有空闲线程,就去取出任务执行;
如果没有空闲线程,就新建一个
3.执行完任务的线程有60秒生存时间,
如果在这个时间内可以接到新任务,
就继续,否则结束生命
适用场景
用于并发执行大量短期的小任务
newScheduledThreadPool
(定长、周期性执行任务)
特点
最大线程数为Integer.MAX_VALUE
阻塞队列是DelayedWorkQueue
工作机制
1.线程从 DelayQueue 中获取 time 大于等于当前时间的
ScheduledFutureTask(DelayQueue.take())
2.执行完后修改这个 task 的 time 为下次被执行的时间
3.再把这个 task 放回队列中
适用场景
用于需要多个后台线程执行周期任务,
同时需要限制线程数量的场景。
两种启动线程池的方法:submit(有返回值)和execute(无返回值)
线程池的核心
生产者消费者模型
生产者将需要处理的任务放入队列
消费者从任务队列中取出任务处理
线程池的参数(ThreadPoolExecutor)
maximumPoolSize:最大线程数
corePollSize:核心线程数
keepAliveTime:空闲的线程保留的时间
workQueue任务队列):
用于保存等待执行的任务的阻塞队列
ThreadFactory
用于设置创建线程的工厂,可以通过线程工厂给每个
创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等
RejectedExecutionHandler(饱和策略)<br>
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
TimeUnit:空闲线程的保留时间单位
单机上一个线程正在处理服务,如果忽然断电了怎么办
(正在处理和阻塞队列里的请求怎么处理)
阻塞队列持久化,正在处理事物控制。断电之后正在处理的回滚,
日志恢复该次操作。服务器重启后阻塞队列中的数据再加载
线程池关闭相关操作
shutdown<br>
shutdown()后线程池将变成shutdown状态,此时不接收新任务,
但会处理完正在运行的 和 在阻塞队列中等待处理的任务。
shutdownNow
shutdownNow()后线程池将变成stop状态,此时不接收新任务,<br>不再处理在阻塞队列中等待的任务,还会尝试中断正在处理中的工作线程。<br>
阻
塞
队
列
支持两个附加操作
当队列满时,存储元素的线程会等待队列可用
当队列空时,获取元素的线程会等待队列变为非空
阻塞队列先设定大小,防止内存溢出
线
程
池
队
列
ArrayBlockingQueue
(有界队列)
是一个基于数组结构的有界阻塞队列,
此队列按 FIFO(先进先出)原则对元素进行排序
可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的
元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当
ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的
线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素
尝试加入ArrayBlockingQueue时会报错
LinkedBlockingQueue
(无界队列)
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出)
排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法
Executors.newFixedThreadPool()使用了这个队列。
maximumPoolSizes为无效
当前执行的线程数量达到corePoolSize的数量时,
剩余的元素会在阻塞队列里等待
SynchronousQueue
(同步队列)
一个不存储元素的阻塞队列。每个插入操作必须等到
另一个线程调用移除操作,否则插入操作一直处于阻塞状态,
吞吐量通常要高于LinkedBlockingQueue,静态工厂方法
Executors.newCachedThreadPool使用了这个队列。
maximumPoolSizes为无界
使用SynchronousQueue阻塞队列一般要求
maximumPoolSizes为无界,避免线程拒绝执行操作
特点
SynchronousQueue 队列中没有任何缓存的数据,
可以理解为容量为 0
SynchronousQueue 提供两种实现方式,
分别是 栈 和 队列 的方式实现。这两种实现方式中,
栈 是属于非公平的策略,队列 是属于公平策略
DelayQueue
(延迟队列)
一个任务定时周期的延迟执行的队列。
根据指定的执行时间从小到大排序,
否则根据插入到队列的先后排序。
PriorityBlockingQueue(优先级队列)
一个具有优先级得无限阻塞队列
判断线程是否停止的方法interrupted
和isinterrupted的区别
interrupted
静态方法
作用于当前正在运行的线程
会清除线程中断状态
isinterrupted
非静态方法
作用于该方法的调用对象所对应的线程
三个线程交替顺序打印ABC
使用synchronized, wait和notifyAll
使用Lock->ReentrantLock 和 state标志
使用Lock->ReentrantLock 和
Condition(await 、signal、signalAll)
使用Semaphore
使用AtomicInteger
多线程中join()方法
t.join()方法只会使主线程进入等待池并等待t线程
执行完毕后才会被唤醒。并不影响同一时刻处在
运行状态的其他线程
缓存一致性问题
加lock锁
缓存一致性协议
ThreadLocal
相当于一个容器,用于存放每个线程的局部变量
底层也是封装了ThreadLocalMap集合类来绑定当
前线程和变量副本的关系,各个线程独立并且访问安全
为什么ThreadLocalMap的
Entry是一个weakReference?
能够在下次gc时被回收,回收后的空间能够
得到复用,在一定程度下能够避免内存泄露
还适用的场景
数据库连接、Session管理
sleep()和wait
sleep是Thread类的方法,sleep方法只让出了CPU,而并不会释放同步资源锁
当sleep()结束指定休眠时间后,不一定立即执行,此时其他线程可能正在运行
wait方法是Object类里的方法,线程执行到wait()方法后,进入待池中,
同时释放了锁对象,必须调用notify或者notifyAll方法才能唤醒,而且是随机唤醒,
若是被其他线程抢到了CPU执行权,该线程会继续进入等待状态
是否可以传入参数
sleep()方法必须传入参数,参数就是休眠时间
wait()方法可以传入参数也可以不传入参数,
传入参数就是在参数结束的时间后开始等待
是否需要捕获异常
sleep方法必须要捕获异常
wait方法不需要捕获异常
作用范围
wait、notify和notifyAll方法只能在同步方法或者同步代码块中使用
sleep方法可以在任何地方使用,注意sleep是静态方法,也就是说它只对当前对象有效
调用者的区别
wait和notify、notifyAll方法是由确定的对象即锁对象来调用的,
先对某个线程说停下来等待,然后对另一个线程说可以执行了
sleep方法是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,
运行的主动权是由当前线程来控制(拥有CPU的执行权)
本质的区别一个是线程的运行状态控制,一个是线程间的通信
notify和notifyAll
唤醒一个或多个正处于等待状态的线程
run和start
调用start()后,线程会被放到等待队列,等待CPU调度,
不一定要马上开始执行,只是将这个线程置于可动行状态。
然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体
锁优化
减少锁的持有时间
减少锁得用于粒度
锁分离
读锁
写锁
锁粗化
如果一段程序要多次请求锁,锁之间的代码执行时间比较少,就应该整合成一个锁
自旋锁(不可重入锁)
锁清除
堆检测到不可能存在共享数据竞争的锁进行清除(逃逸分析技术)
锁降级
指的是把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前有用的)写锁的过程
volatile
可见性
底层是共享变量
禁止重排序
内存屏障
LoadLoad
StoreStore
LoadStore
StoreLoad
内部实现机制
volatile变量修饰的共享变量的Java代码转换成汇编代码,会有lock 修饰
将当前内核高速缓存行的数据立刻回写到内存
使在其他内核里缓存了该内存地址的数据无效
MESI协议,该解决缓存一致性的思路是:当CPU写数据时,如果发现操作的变量是共享
变量,即在其他CPU中也存在该变量的副本,那么他会发出信号通知其他CPU将该变量
的缓存行设置为无效状态。当其他CPU使用这个变量时,首先会去嗅探是否有对该变量更
改的信号,当发现这个变量的缓存行已经无效时,会从新从内存中读取这个变量。
lock 指令前缀
当使用 LOCK 指令前缀时,它会使 CPU 宣告一个 LOCK# 信号,
确保互斥地使用这个内存地址,当指令执行完毕,锁消失
lock前缀指令相当于一个内存屏障,会强制将对缓存的修改操作写入主内存
处理器的嗅探技术
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,
当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效态,
当处理器对这个数据进行修改操作的时候,会重新从系统内存中吧数据读到处理器缓存里
无锁化编程实现线程安全
final(数据不可修改)
CAS
原子类
Copy-on-write
读写锁
写独占,读共享,写锁优先级高(本质上是一种自旋锁)
如何判断一个线程是否拥有锁
Thread的holdsLock方法
happens-before原则
如果前一个操作(A)必须要对后一个操作(C)可见 ,那么这两个操作(A C) 指令不能重排
乐观锁(适用于大并发量)
CAS
atomic
悲观锁(适用于并发量不大的场景)
Lock
悲观锁、可中断锁、可公平锁、可重入锁)
synchronized
悲观锁、不可中断锁、非公平锁、可重入锁)
底层通过操作系统的互斥量实现,适用于竞争不激烈的场景
synchronized
原理
Synchronized的语义底层是通过一个monitor的对象来完成
当monitor被占用时就会处于锁定状态,线程执行
monitorenter指令时尝试获取monitor的所有权
如果monitor的进入数为0,则该线程进入monitor,
然后将进入数设置为1,该线程即为monitor的所有者
如果线程已经占有该monitor,只是重新进入,
则进入monitor的进入数加1
其他线程已经占用了monitor,则该线程进入阻塞状态,
直到monitor的进入数为0,再重新尝试获取monitor的所有权
Synchronized底层优化
(偏向锁、轻量级锁)
偏向锁
为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径
轻量级锁
锁可以从偏向锁升级到轻量级锁,再升级的重量级锁
轻量级锁的获取及释放依赖多次CAS原子指令
适应性自旋
指定自旋的次数
线程如果自旋成功了,则下次自旋的次数会更多,
如果自旋失败了,则自旋的次数就会减少
这样做是为了减少消耗CPU
锁粗化
将多次连接在一起的加锁、解锁操作合并为一次
锁消除
根据代码逃逸技术,删除不必要的加锁操作
synchronized 修饰
对象锁
synchronized修饰类的非静态方法
对一个非静态成员变量进行synchronized修饰
对于同步方法块,锁是Synchronized括号里配置的对象
类锁
synchronized修饰静态方法或者class
对于对象锁,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞
无论是类锁还是对象锁,父类和子类之间是否阻塞没有直接关系
可重入锁和不可重入锁(自旋锁)
可重入锁实现原理:当一个线程请求成功后,JVM会记下持有锁的线程,
并将计数器计为1。此时其他线程请求该锁,则必须等待;
而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增
synchronized 和 volatile 的区别是什么?
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,
需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以
访问该变量,其他线程被阻塞住
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;
而synchronized则可以保证变量的修改可见性和原子性
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
synchronized 和 ReentrantLock 区别是什么
synchronized是关键字、ReentrantLock是类
ReentrantLock是类,那么它就提供了比synchronized
更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变
ReentrantLock可以对获取锁的等待时间进行设置
ReentrantLock可以获取各种锁的信息
sychronized是不可中断锁、非公平锁;ReentrantLock是可中断,可公平锁
ReentrantLock需在finally中手工释放锁
高并发系统限流中的算法
漏桶算法
一个固定容量的漏桶,按照固定常量速率流出请求,
流入请求速率任意,当流入的请求数累积到漏桶容量时,
则新流入的请求被拒绝
令牌桶算法
一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,
填满了就丢弃令牌,请求是否被处理要看桶中令牌是否足够,
当令牌数减为零时则拒绝新的请求
计数器限流算法
用来限制一定时间内的总并发数,
比如数据库连接池、线程池、秒杀的并发数
锁的状态
无锁状态
偏向锁状态
偏向锁是指一段同步代码一直被一个线程所访问,
那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁状态
轻量级锁是指当锁是偏向锁的时候,被另一个线程
所访问,偏向锁就会升级为轻量级锁,其他线程会
通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁状态<br>
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,
但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,
就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线
程进入阻塞,性能降低。
JUC
Semaphore
Semaphore翻译成字面意思为 信号量,Semaphore可以控制同时访问的线程
个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可
CountDownLatch
CountDownLatch 类中主要的方法
await 当前线程等待直到计数器为0
countDown(); //调用此方法则计数减1
使用场景
开启多个线程分块下载一个大文件,每个线程只下载固定的一截,
最后由另外一个线程来拼接所有的分段
应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行
确保一个计算不会执行,直到所需要的资源被初始化
原理
在并发环境下由线程进行减1操作,当计数值变为0之后,
被await方法阻塞的线程将会唤醒,实现线程间的同步
与CyclicBarrier的区别
CountDownLatch强调一个线程等多个线程完成某件事情,
CyclicBarrier是多个线程互等,等大家都完成
CountDownLatch减计数,CyclicBarrier加计数
CountDownLatch是一次性的,CyclicBarrier可以重用
CyclicBarrier
循环屏障,让一组线程到达一个屏障(同步点)被阻塞,直到最后一个线程到达屏障时,屏障才打开
SynchronousQueue
LinkedBlockingQueue
ArrayBlockingQueue
ConcurrentHashMap
Future
FutureTask
ReentrantLock
适用于竞争激烈的场景
ReentrantLock的API里面,
lock()和trylock()的区别?
lock函数是阻塞的
trylock()是非阻塞的,调用后立即返回
CopyonWriteArrayList和CopyOnWriteArraySet
原理:读写分离,读支持高并发,写需要加锁
问题
内存占用
数据一致性问题,支持最终一致性,不支持实时一致性
CopyOnWriteArrayList, CopyOnWriteArraySet和ConcurrentSkipListSet
CopyOnWriteArrayList, CopyOnWriteArraySet和ConcurrentSkipListSet
设计模式
单例模式
懒汉
synchroinzed
饿汉
双重校验锁
volatile/synchronized
使用了volatile关键字后,重排序被禁止
https://www.cnblogs.com/xz816111/p/8470048.html
实例化对象的那行代码,
可以分解成三个步骤
分配内存空间
初始化对象
将对象指向刚分配的内存空间
编译器为了性能的原因,可能会将第
二步和第三步进行重排序,顺序就成了
分配内存空间
将对象指向刚分配的内存空间
初始化对象
T7时刻线程B对uniqueSingleton的访问,
访问的是一个初始化未完成的对象
分支主题
使用场景:网站计数器、windows系统的任务管理器、线程池、缓存、日志对象、对话框对象常被设计成单例
观察者模式
zookeeper的设计模式
装饰者模式
java I/O库是由一些基本的原始流处理器和
围绕它们的装饰流处理器所组成的
适配器模式
Java的I/O类库中有许多这样的需求,如将字符串转成字节数据保存到文件中,将字节数据变成数据流等。
具体来说,InputStreamReader和OutputStreamWriter就是适配器的体现
工厂模式
spring将对象的创建及初始化职责交给工厂对象
代理模式
静态代理和动态代理的区别
在程序运行前就已经存在代理类的字节码文件,
代理类和委托类的关系在运行前就确定了。
静态代理通常只代理一个类
代理类和委托类的关系是在程序运行时确定。
动态代理是代理一个接口下的多个实现类
设计模式之六大原则
单一职责原则
一个类只负责一个功能领域中的相应职责
开闭原则
一个软件实体应当对扩展开放,对修改关闭
里氏替换原则
所有引用基类(父类)的地方必须能透明地使用其子类的对象
依赖倒置原则
需要针对抽象层编程,而将具体类的对象通
过依赖注入(DependencyInjection, DI)的方式注入到其他对象中
接口隔离原则
当一个接口太大时,我们需要将它分割成一些更细小的接口
迪米特法则
一个模块发生修改时,应该尽量少地影响其他模块
JDK1.9特性
Java 平台模块系统的引入
明确依赖
判断所有需要的 JAR 是否都已经有了,以及是否有重复项
Linking
Java 9 中的新的 jlink 工具实现。使得可以创建针对应用程序
进行优化的最小运行时映像而不需要使用完全加载 JDK 安装版本。
JShell
具有交互式编程环境
从控制台启动 jshell ,并直接启动输入和执行 Java 代码
集合工厂方法
支持创建一个集合(例如,List 或 Set ),并直接用一些元素填充它
Set<Integer> ints = Set.of(1,2,3);
JDK8的特性
Optional
最常见的bug就是空值异常
如果Optional实例持有一个非空值,则isPresent()方法返回true,
否则返回false;orElseGet()方法,Optional实例持有null,则可以
接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional
实例的值转换成新的值;orElse()方法与orElseGet()方法类似
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器
Stream API −新添加的Stream API,
允许以声明性方式处理数据集合
JDK8 Stream
详细使用
Stream 的获取
通过集合Collection获取
通过数组获取
直接通过值获取
Stream 常用管道操作
筛选 filter
去重 distinct
截取流的前N个元素:limit
跳过流的前n个元素skip
合并多个流 flatMap
归约统计:求最值、均值
Date Time API − 加强对日期与时间的处理
Lambda 表达式 Lambda允许
把
函数作为一个方法的参数
lambda 表达式
的优缺点总结
什么是lambda表达式
https://blog.csdn.net/weixin_40839731/article/details/79594897
优点
简洁
非常容易并行计算
可能代表未来的编程趋势
结合 hashmap 的 computeIfAbsent 方法,
递归运算非常快。java有针对递归的专门优化
缺点
不容易调试
lambda 语句中强制类型不方便
拓宽注解的应用场景
注解几乎可以使用在任何元素上:局部变量、接口类型、
超类和接口实现类,甚至可以用在函数的异常定义上
并行数组
方法parallelSort()用于支持并行数组处理
java三大器
过滤器
原理
函数回调
适用场景
url级别的权限访问控制
过滤敏感词汇
监听器
原理
基于事件
适用场景
统计网站在线人数
清除过期session
拦截器(Interceptor)
概念
拦截器用于在某个方法或者字段被访问之前,进行拦截
然后再之前或者之后加入某些操作
原理
基于JDK实现的动态代理
场景
拦截未登录用户
审计日志
Spring拦截器:HandlerInterceptor接口和HandlerInterceptorAdapter类
常见java性能优化手段
尽量指定类、方法的final修饰
如果指定了一个类为final,则该类所有的方法都是final的
Java编译器会寻找机会内联所有的final方法,
内联对于提升Java运行效率作用重大
尽量重用对象
Java虚拟机要花时间生成对象
也要花时间对这些对象进行垃圾回收和处理
尽可能使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量
都保存在栈中,速度较快,其他变量,如静态变量、
实例变量等,都在堆中创建,速度较慢
栈中创建的变量,随着方法的运行结束,
这些内容就没了,不需要额外的垃圾回收
及时关闭流
进行数据库连接、I/O流操作时及时关闭以释放资源
减少对变量的重复计算
for (int i = 0; i < list.size(); i++)
{...}
替换为
for (int i = 0, length = list.size(); i < length; i++)
{...}
采用懒加载的策略
在需要的时候才创建
慎用异常
异常被抛出,Java虚拟机须调整调用堆栈
因为在处理过程中创建了一个新的对象
如果能估计到待添加的内容长度,
为底层以数组方式实现的集合、
工具类指定初始长度
比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等
给底层以数组实现的集合、工具类设置一个合理的初始化容量
既节省内存空间又提高代码运行效率
循环内不要不断创建对象引用
for (int i = 1; i <= count; i++)
{
Object obj = new Object();
}
导致内存中有count份Object对象引用存在
建议为改为:
Object obj = null;
for (int i = 0; i <= count; i++)
{
obj = new Object();
}
乘法和除法使用移位操作
单线程下尽量使用HashMap、
ArrayList、StringBuilder
无法确定数组大小时才使用ArrayList,
可以确定数组大小时尽量使用array
使用同步代码块替代同步方法
避免对那些不需要进行同步的代码也
进行了同步,影响了代码执行效率
不要创建一些不使用的对象,不要导入一些不使用的类
尽量避免随意使用静态变量
及时清除不再需要的会话
使用数据库连接池和线程池
使用带缓冲的输入输出流进行IO操作
BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream
缓存流为IO流增加了内存缓存区,这可以极大地提升IO效率
顺序插入和随机访问比较多的场景使用ArrayList,
元素删除和中间插入比较多的场景使用LinkedList
字符串变量和字符串常量equals
的时候将字符串常量写在前面
String str = "123";
if (str.equals("123"))
{
...
}
建议修改为:
String str = "123";
if ("123".equals(str))
{
...
}
主要是可以避免空指针异常
不要让public方法中有太多的形参,public方法即对外提供的方法,
如果给这些方法太多形参的话主要有两点坏处
违反了面向对象的编程思想,Java讲求一切都是对象,
太多的形参,和面向对象的编程思想并不契合
参数太多势必导致方法调用的出错概率增加
公用的集合类中不使用的数据一定要及时remove掉
把一个基本数据类型转为字符串,基本数据类型.toString()
是最快的方式、String.valueOf(数据)次之、数据+""最慢
不要对数组使用toString()方法
结果是地址
对资源的close()建议分开操作
try
{
XXX.close();
YYY.close();
}
catch (Exception e)
{
...
}
建议修改为:
try
{
XXX.close();
}
catch (Exception e)
{
...
}
try
{
YYY.close();
}
catch (Exception e)
{
...
}
能避免资源泄露
如果没有修改过的代码,万一XXX.close()抛异常了,
那么就进入了catch块中了,YYY.close()不会执行
对于ThreadLocal使用前或
者使用后一定要先remove
线程池技术做的是一个线程重用,这意味着代码运行过程中,
一条线程使用完毕,并不会被销毁而是等待下一次的使用
下一条线程重用这个Thread的时候,很可能get
到的是上条线程set的数据而不是自己想要的内容
以常量定义的方式替代魔鬼数字,
魔鬼数字的存在将极大地降低代码可读性,
字符串常量是否使用常量定义可以视情况而定
所有重写的方法必须
保留@Override注解
可以知道这个方法由父类继承而来
getObject()和get0bject()方法,
前者第四个字母是"O",后者第四个子母是"0",
加了@Override注解可以马上判断是否重写成功
在抽象类中对方法签名进行修改,实现类会马上报出编译错误
long或者Long初始赋值时,使用大写的L而不是小写的l,因为字母l极易与数字1混淆
推荐使用JDK7中新引入的Objects工具类
来进行对象的equals比较,直接a.equals(b),
有空指针异常的风险
循环体内不要使用"+"进行字符串拼接,
而直接使用StringBuilder不断append
每次虚拟机碰到"+"这个操作符对字符串进行拼接的时候,
会new出一个StringBuilder,然后调用append方法,
最后调用toString()方法转换字符串赋值给oriStr对象
循环多少次,就会new出多
少次,这对于内存是一种浪费
异常处理效率低,规避一些异常处理
ArithmeticException可以通过判断除数是否为空来规避
NullPointerException可以通过判断对象是否为空来规避
IndexOutOfBoundsException可以通过判断数组/字符串长度来规避
ClassCastException可以通过instanceof关键字来规避
ConcurrentModificationException可以使用迭代器来规避
静态类、单例类、工厂类将它
们的构造函数置为private
将构造函数置为private之后,
保证了这些类不会产生实例对象
避免Random实例被多线程使用,虽然共享该实例是
线程安全的,但会因竞争同一seed 导致的性能下降,
JDK7之后,可以使用ThreadLocalRandom来获取随机数
Collect
Get Started
Collect
Get Started
Collect
Get Started
Collect
Get Started
评论
0 条评论
下一页