-
接口/抽象类意义
- 规范、扩展、回调
- 为其子类提供一个公共的类型 封装子类中得重复内容 定义抽象方法,子类虽然有不同的实现 但是定义是一致的
- 接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约、而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法
-
多态的理解
- 表现:
- 多态性是指允许不同子类型的对象对同一消息作出不同的响应
- 多态性分为编译时的多态性和运行时的多态性,方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写 (override)实现的是运行时的多态性(也称为后绑定)术为动态绑定,具体就是父类的引用指向子类的对象
- 实现多态需要做两件事:
- 方法重写(子类继承父类并重写父类中已有的或抽象的方法)
- 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)
- 优点:
- 可扩张性好,无论添加多少个子类,基类的接口都不用改变,只需要在子类对应方法中提供具体实现即可,也就是所谓的将程序变化的部分和程序保持不变的部分分离
- 运行时多态性是面向对象程序设计代码重用的一个最强大机制
- 其它规则:
- 只有正常的方法可以使用多态,字段和静态方法没有多态机制
- 构造方法也不支持多态机制,构造方法是隐式的static声明
- 解析与分派
- 实现原理:
- 当JVM执行Java字节码时,类型信息会存储在方法区中,为了优化对象的调用方法的速度,方法区的类型信息会增加一个指针,该指针指向一个记录该类方法的方法表,方法表中的每一个项都是对应方法的指针
- 由于java的单继承机制,一个类只能继承一个父类,而所有的类又都继承Object类,方法表中最先存放的是Object的方法,接下来是父类的方法,最后是该类本身的方法。如果子类改写了父类的方法,那么子类和父类的那些同名的方法共享一个方法表项
- 由于这样的特性,使得方法表的偏移量总是固定的,例如,对于任何类来说,其方法表的equals方法的偏移量总是一个定值,所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值
- 流程:调用方法时,虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口,查询类的方法表 ,根据实例方法的符号引用解析出该方法在方法表的偏移量,子类对象声明为父类类型时,形式上调用的是父类的方法,此时虚拟机会从实际的方法表中找到方法地址,从而定位到实际类的方法。 注:所有引用为父类,但方法区的类型信息中存放的是子类的信息,所以调用的是子类的方法表
- 表现:
-
抽象类理解
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
- 抽象类不能用来创建对象,不能直接实例化,但可以被声明(程序运行期在内存中分配一定空间,而抽象类总抽象方法没有具体的实现方法无法分配具体的内存空间)
- 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
- 如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法
- 抽象类可以继承抽象类、非抽象类,实现接口
-
抽象类和接口的区别
- 抽象类是abstract class修饰,接口是interface修饰。
- 抽象类只能单继承,接口可以多重实现。
- 抽象类和接口所反映的设计理念是不同的,抽象类所代表的是“is-a”的关系,而接口所代表的是“like- a”的关系
- 抽象类可以有任意类型的属性,接口成员变量只能是public static final类型的。
- 抽象类可以有普通方法和抽象法方法,接口的方法(public abstract修饰)都没有方法体(普通类实现接口必须全部实现接口方法)
- 抽象类和接口都不能实例化,但是抽象类有构造方法,接口没有构造方法。
- 抽象类可以有静态代码块和静态方法,而接口中不能含有静态代码块以及静态方法
-
类型转换相关
- 自动转换按从低到高的顺序转换。不同类型数据间的优先关系如下:
byte,short,char-> int -> long -> float -> double
- 数值型变量在默认情况下为Int型,byte和short型在计算时会自动转换为int型计算,结果也是int 型
-
运算中,不同类型的数据先转化为同一类型,然后进行运算,转换规则如下:
- 对于一个final变量,不管它时类变量、实例变量、还是局部变量,只要定义该变量时使用了final修饰,并在定义该final类变量时指定了初始值,而且该初始值可以在编译时被确定下来,那么这个final变量实质上已经不是变量,相当于一个恒量或者是直接量
- 自动转换按从低到高的顺序转换。不同类型数据间的优先关系如下:
-
关于String的问题
- 任何类任何包,值相同的字符串字面常数(String Literals)都引用同一个对象
- 字面常数(Literals)就是你写在源代码里面的值,比如说int i = 6; 6就是一个整数形字面常数。String s = "abc"; “abc”就是一个字符串字面常数
- 所有的字符串字面常数都放在上文提到的字符串池里面,是可以共享的,eg:String s1 = "abc"; String s2 = "abc"; s1,s2都引用的同一个字符串对象,因此s1==s2
- 字符串字面常数是Load Class时候实例化并放到字符串池里面去的
- 通过常量表达式(constant expressions)计算出来的字符串,也算字符串字面常数,就是说他们也在字符串池中
- 常量表达式编译时能确定的
-
final String s2 = "a"; String s3 = s2 + "bc";
被final修饰,并且通过常量表达式初始化的变量,也算常量表达式 - 在程序运行时通过连接(+)计算出来的字符串对象,是新创建的,他们不是字面常数,就算他们值相同,他们也不在字符串池里面,他们在堆内存空间里,因此引用的对象各不相同
- String类的intern方法,返回一个值相同的String对象,但是这个对象就像一个字符串字面常数一样,意思就是,他也到字符串池里面去了
-
基本类型、包装类、String
- 包装类提供的xxxValue()方法将包装类对象转化成基本类型变量
Integer iObj = new Integer(i); int m = iObj.intValue();
- 基本数据类型转化为包装类类型
- 构造方法把基本型转成包装类
- 用valueOf 转成包装类
- 除Character外所有的包装类提供parseXXX()方法将特定的字符串转换成基本类型变量
- String类提供了valueOf()方法将基本类型比那里转换成字符串
- 包装类提供的xxxValue()方法将包装类对象转化成基本类型变量
-
super关键字作用
- super.xxx;(xxx为变量名或对象名):获取父类中的名字为xxx的变量或方法引用。使用这种方法可以直接访问父类中的变量或对象,进行修改赋值等操作
- super.xxx();(xxx为方法名):直接访问并调用父类中的方法
- super();:调用父类的初始化方法,其实就是调用父类中的public xxx()方法
- 子类、父类方法调用优先级由高到低依次为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)
-
内部类相关
-
作用:
- 内部类可以很好的实现隐藏,除了该外围类,其他类都不能访问
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类, 弥补在多重继承上的不足
- 匿名内部类可以方便的实现闭包
-
类型:
- 静态内部类(Static Nested Class):使用static修饰的内部类,当内部类中定义静态成员,该内部类必须是static,不持有外部类引用,不依赖外部类(它不能使用任何外围类的非static成员变量和方法,只能访问外部类的静态成员)
- 成员内部类(Inner Class):就是在某个类的内部又定义了一个类,内部类所嵌入的类称为外部类,成员内部类中不能存在任何 static 的变量和方法,可以直接访问外部类中的成员变量
- 匿名内部类:使用new生成的内部类,没有构造器,取而代之的是将构造器参数传递给超类构造器
- 局部内部类:不能用访问控制符、static修饰,只能访问被final修饰的局部变量(jdk8可以不用)
-
访问规则:
- 内部类可以直接访问外部类成员,外部类要访问内部类,必须建立内部类对象
- 因为内部类的产生依赖于外部类,持有的引用是类名.this
-
静态方法中不能new内部类的实例对象?
内部类的最重要的一个特点就是它可以直接访问它外部类的成员变量。成员变量是对象身上的。对象创建完成了,才会为成员变量分配空间。能调用成员变量,意味着一定有了实例对象
关于初始化:
public class Enclosingone { public class InsideOne {} //非静态内部类 public static class InsideTwo{} //静态内部类 } class Mytest02{ public static void main(String args []){ Enclosingone.InsideOne obj1 = new Enclosingone().new InsideOne();//非静态内部类对象 Enclosingone.InsideTwo obj2 = new Enclosingone.InsideTwo();//静态内部类对象 } }
-
-
重载/重写
- 方法重载(overload):
- 必须是同一个类
- 方法名(也可以叫函数)一样
- 参数类型不一样或参数数量不一样
- 存在于父类和子类、同类中
- 方法的重写(override)两同两小一大原则:
- 方法名相同,参数类型相同
- 子类返回类型小于等于父类方法返回类型,
- 子类抛出异常小于等于父类方法抛出异常,
- 子类访问权限大于等于父类方法访问权限。
- 重写(Overriding)是父类与子类之间多态性的一种表现(变量不能被重写),而重载(Overloading)是一个类中多态性的一种表现
- 方法重载(overload):
-
类型&接口
- switch支持类型:byte, short, char, int, 枚举 ,String(jdk7)
- Java引用类型:类、接口、数组、枚举、注解
- 标志性接口:RandomAccess和Cloneable 、Serializable不需要任何实现,只是又来表明其实现类具体有某种特质
-
Object方法
- final方法:getClass、wait、notify、notifyAll
- 非final方法:equals、hashCode、toString、clone、finalize
-
hashCode() 和equals() 区别和作用
- 区别:
- equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false
- HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的
- 访问原理:
- hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回(方法集合中用到)
- 对象放入集合时,首先判断放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入
- 重写规则:
- 重写equals方法时,应该要重写hashcode方法
- 两者重写时候关系:
- 如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等
- 如果两个对象不equals,他们的hashcode有可能相等
- 如果两个对象hashcode相等,他们不一定equals
- 如果两个对象hashcode不相等,他们一定不equals
- 区别:
-
集合框架图
-
Java集合框架的基础接口有哪些?
- Collection为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现
- Set是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌
- List是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组
- Map是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value
- 一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator
-
为何Collection不从Cloneable和Serializable接口继承?
- Collection接口指定一组对象,对象即为它的元素。如何维护这些元素由Collection的具体实现决定。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现
- 当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化
- 在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制。特定的实现应该决定它是否可以被克隆和序列化
-
为何Map接口不继承Collection接口?
- 尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。
- 如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范
-
HashMap与HashSet的区别
- HashSet为散列集,其底层采用的是HashMap进行实现的,但是没有key-value(即它不需要Key和Value两个值),只有HashMap的key-set的视图,HashSet不容许重复的对象
- 调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的哪个对象,该行的value就是一个Object类型的常量
-
HashMap与TreeMap区别
- HashMap 通过 hashcode 对其内容进行快速查找,而 TreeMap 中所有的元素都保持着某种固定的顺 序,如果你需要得到一个有序的结果你就应该使用 TreeMap(HashMap 中元素的排列顺序是不固定的)。Ha shMap 中元素的排列顺序是不固定的)。
- 在 Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历 键,那么 TreeMap 会更好。使用 HashMap 要求添加的键类明确定义了 hashCode() 和 equals() 的实现。 这 个 TreeMap 没有调优选项,因为该树总处于平衡状态
-
HashMap和Hashtable的区别
- Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现
- Hashtable中key和value都不允许为null,而HashMap中key和value都允许为null(key只能有一个为null,而value则可以有多个为null)
- Hashtable的方法是Synchronize的,而HashMap不是
- HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历
- HashTable直接使用对象的hashCode。而HashMap重新计算hash值
- HashTable中hash数组默认大小是 11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数
-
Iterator和Enumeration的区别
- Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改,Iterator只有3个函数接口,Iterator除了能读取集合的数据之外,也能数据进行删除操作
- Iterator支持fail-fast机制,而Enumeration不支持
- fail-fast 机制是java集合(Collection)中的一种错误机制,当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件,就会抛出ConcurrentModificationException异常
-
ArrayList和Vector的区别
- Vector:线程同步,ArrayList:线程不同步,但性能很好
- 当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍,当ArrayList中的元素超过它的初始大小时(默认初始长度为10),ArrayList只增加50%的大小
- 可以用Enumeration接口遍历
-
Iterator和Iterable区别
- Iterator是迭代器类,而Iterable是接口
- 一个类实现了Iterable,那么虚拟机就知道,你这个类可以用foreach来循环
- 集合Collection、List、Set都是Iterable的实现类,所以他们及其他们的子类都可以使用foreach进行迭代
- Iterator中和核心的方法next(),hasnext(),remove(),都是依赖当前位置,如果这些集合直接实现Iterator,则必须包括当前迭代位置的指针。当集合在方法间进行传递的时候,由于当前位置不可知,所以next()之后的值,也不可知。而当实现Iterable则不然,每次调用都返回一个从头开始的迭代器,各个迭代器之间互不影响。
-
HashMap的工作原理
- 结构:
- HashMap是数组+链表+红黑树(JDK1.8增加)实现的,数组里存储的元素是一个Node实体(JDK1.8为Node,之前为Entry),这个Node实体主要包含hash(定位数组索引位置)、key、value以及next指针(指向Node)
- JDK1.8当链表长度超过8时候引入红黑树,增删改查的特点提高HashMap的性能
- 操作:
- 当
put()
时,根据键值key计算hash值得到数组索引i,如果数组值为空则直接新建节点添加,否则判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,哈希碰撞利用链表插入,大于8的话把链表转换为红黑树,在红黑树中执行插入操作 - 当
get()
时,获取指定key的值时,会根据这个key算出它的hash值(数组下标),根据这个hash值获取数组下标对应的Node,然后判断Node里的key,hash值或者通过equals()比较是否与要查找的相同,如果相同,返回value,否则的话,遍历该链表(有可能就只有一个Node,此时直接返回null),长度大于8则变量红黑树节点,直到找到为止,否则返回null - 扩容机制resize:
- 当
- 哈希碰撞:
- Hash算法本质上就是三步:取key的hashCode值、高位运算(
h ^ (h >>> 16)
)、取模运算 - HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象
- Hash算法本质上就是三步:取key的hashCode值、高位运算(
- 扩容机制:
- DEFAULT_INITIAL_CAPACITY 是初始容量,默认是 1 << 4 = 16
- DEFAULT_LOAD_FACTOR 是负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,扩充2倍,例如,当前大小是16,当占用超过160.75=12时,就把容量扩充到162=32
- MAXIMUM_CAPACITY是最大容量,默认是 2^30
- 当两个不同的键对象的hashcode相同时会发生什么?
它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对。
- 可以使用CocurrentHashMap来代替HashTable吗?
ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap可以代替HashTable,但是HashTable提供更强的线程安全性。
- HashMap源码剖析
- 结构:
-
LinkedHashMap的实现原理
- LinkedHashMap也是基于HashMap实现的,不同的是它定义了一个Entry header,这个header不是放在Table里,它是额外独立出来的。LinkedHashMap通过继承hashMap中的Entry,并添加两个属性Entry before,after,和header结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true;对于插入顺序,则为false。一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序
- LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的
-
ConcurrentHashMap原理
- ConcurrentHashMap采用 分段锁的机制,实现并发的更新操作,底层采用数组+链表+红黑树的存储结构,其包含两个核心静态内部类 Segment和HashEntry
- 默认创建包含16个Segment对象的数组,首先将数据分成一段一段的存储(Segment继承ReentrantLock),每个 Segment 对象守护每个散列映射表的若干个桶(每个桶是由若干个 HashEntry 对象链接起来的链表),当一个线程占用锁访问其中一个数据时,其它段的数据也能被其它的线程访问修改,而Hashtable锁的是整个hash表,效率较低
- JDK1.8的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构
- ConcurrentHashMap的size操作
- ConcurrentHashMap源码分析
-
静态方法与单例模式的区别
- 单例可以继承类,实现接口,而静态类不能(可以集成类,但不能集成实例成员)
- 单例可以被延迟初始化,静态类一般在第一次加载是初始化
- 单例类可以被集成,他的方法可以被覆写
- 单例类可以被用于多态而无需强迫用户只假定唯一的实例
- 静态方法中产生的对象,会随着静态方法执行完毕而释放掉,而且执行类中的静态方法时,不会实例化静态方法所在的类。如果是用singleton, 产生的那一个唯一的实例,会一直在内存中,不会被GC清除的(原因是静态的属性变量不会被GC清除),除非整个JVM退出了
-
普通代码块,构造代码块,静态代码块区别
- 普通代码块:在方法或语句中出现的{}就称为普通代码块。普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定--“先出现先执行”
- 构造代码块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数
- 静态代码块:static关键字修饰,用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行
- 执行顺序:静态代码块>mian方法>构造代码块>构造方法
-
类的初始化顺序
- 父类的静态变量/静态初始化块;
- 子类类的静态变量/静态初始化块;
- 父类的动态初始化块(与静态初始化块类似,只是没有static关键字,即放在一对大括号中的代码块)、非构造方法和set方法的成员变量初始化;
- 父类的构造方法;
- 子类的动态初始化块、非构造方法和set方法的成员变量初始化;
- 子类的构造方法;
- 父类本地变量;
- 子类的本地变量;
-
关于构造函数
- 子类的构造方法总是先调用父类的构造方法,如果子类的构造方法没有明显地指明使用父类的哪个构造方法,子类就调用父类不带参数的构造方法
- 如果父类没有无参的构造函数,子类则需要在自己的构造函数中显示的调用父类的构造函数,super("nm");
-
向上转型和向下转型
- 向上转型:子类引用的对象转换为父类类型称为向上转型。通俗地说就是是将子类对象转为父类对象。此处父类对象可以是接口(安全)
- 向下转型:父类引用的对象转换为子类类型称为向下转型。
- 如果父类引用的对象如果引用的是指向的子类对象,那么在向下转型的过程中是安全的。也就是编译是不会出错误的。
- 如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现java.lang.ClassCastException错误。它可以使用instanceof来避免出错此类错误。
-
try-catch-finally执行顺序
- 不管有木有出现异常,finally块中代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
- finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
- 任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的 - try中的return语句调用的函数先于finally中调用的函数执行,也就是说return语句先执行(但是还没有返回),finally语句后执行
-
JDK新特性
- JDK5.0新特性系列
- JDK7
- Swith判断可以用String类型
- JDK8
- Lambda表达式,函数式接口
- Stream API
-
注解相关
- 理解:
- 元注解:
@Documented @Retention @Target @Inherited
- 标准注解:
@Override @Deprecated @SuppressWarnings
- 元注解:
- 读取注解信息:
- 获得注解修饰反射对象 Class Field Method ---- 这些所有反射对象 都实现了 AnnotatedElement接口
- 通过 AnnotatedElement 接口 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断目标注解是否存在
- 通过 AnnotatedElement 接口
getAnnotation(Class<T> annotationClass)
获得目标注解
- 理解:
-
泛型相关
- 理解:
- 通配符:
<? super T>
表示包括T在内的任何T的父类,<? extends T>
表示包括T在内的任何T的子类
- 通配符:
- 优点:
- 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能
- 泛型最常见的用途是创建集合类
- 缺点:
- 在性能上不如数组快
- 泛型类:
- 泛型方法:
- 理解:
-
反射相关:
- 理解:
- 反射是一种间接操作目标对象的机制,在程序程序运行时获取或者设置对象自身的信息。 只要给定类的名字,就可以通过反射获取类的所有信息,接着便能调用它的任何一个方法和属性。
- 操作步骤:
- 首先反射机制获取类有三种方法
- Classc1 = Class.forName("Employee");
- Classc2 = Employee.class;
- Classc3 = e.getClass();
- 利用newInstance调用无参数构造方法
- 获取属性、方法、构造方法:有两种分为所有的和特定的
- 利用setAccessible(true)可以访问private修饰的属性或方法
- 首先反射机制获取类有三种方法
- 应用场景:
- 动态代理
- 框架:ButterKnife、EventBus、Dagger
- 插件化:调用系统API、hook对象
- 优化:
- 缓存forName()
- 尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)
- 理解:
-
Java Util Concurrency基础内容概述
- 分类:
- Atomic : AtomicInteger
- Locks : Lock, Condition, ReadWriteLock
- Collections : Queue, ConcurrentMap
- Executer : Future, Callable, Executor
- Tools : CountDownLatch, CyclicBarrier, Semaphore
- 特性:
- 传统锁的问题:synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁
- Atomic类其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升
- 分类:
-
synchronized
- 概述
- synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
- 对象锁&类锁
- 对象锁是用于对象实例方法,或者一个对象实例上的,防止其它线程访问同一对象中的synchronized代码块或者函数
- 类锁是用于类的静态方法或者一个类的class对象上的,防止多个线程同时访问添加了synchronized锁的代码块
- 类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁
- 对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步
- 实现原理
- 同步代码块是使用monitorenter和monitorexit指令实现的,monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置
- synchronized用的锁是存在Java对象头里的monitor标记位,monitor被持有之后,他将处于锁定状态,线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁
- Java对象头
- Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)
- Klass Point是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键
- Mark Word用于存储对象自身的运行时数据,如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等
- 锁优化
- jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销
-
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率
- 概述
-
ThreadLocal与Synchronized的使用场景
- ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度
- synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本, 使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享
-
多个线程访问共享对象和数据的方式
- 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做
- 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享
- 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。
- 将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量
-
sleep()和wait()区别
- sleep是Thread类的方法,wait是Object类的方法。
- sleep是自动唤醒,wait需要其他线程来唤醒。
- sleep不会释放同步锁,wait会释放同步锁。
- sleep可以用在任意方法中,wait只能用在同步方法或同步块中
- sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常
-
Lock
- 简介
- Lock是一个接口,通过这个接口可以实现同步访问;其中ReentrantLock就是Lock接口的实现类
- Lock的分类
- Lock分为“公平锁”和“非公平锁”,公平锁就是先等待的线程先获得锁
- 实现原理
- ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态
- 简介
-
Synchronized 和Lock的用法和区别
- 区别:
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
- 用法:
- Synchronized(隐式锁)在需要同步的对象、方法加入此控制
- Lock(显示锁):需要在指定位置和终止位置上锁和释放锁
- 性能:
- Synchronized是托管给JVM执行的(jdk1.6优化:自旋锁、偏向锁、轻量锁、重量锁)
- 而lock是java写的控制锁的代码,Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁
- 机制:
- Lock使用CAS乐观锁
- Synchronized使用CPU悲观锁
- 区别:
-
Synchronized和volatile区别
- volatile仅在变量级别,Synchronized可以在变量、方法、类
- volatile仅能实现变量的修改可见性,并不能保证原子性,Synchronized则可以保证可见性和原子性
- volatile不会造成线程的阻塞(没加锁),Synchronized会造成线程的阻塞
- volatile标记的变量不会被编译器优化,Synchronized标记的变量可以被编译器优化
-
volatile
volatile无法保证原子性,计数时 i++ 这个操作不是原子的
- 作用:
- 保证内存可见性
- 禁止指令重排序
- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行
- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行
- 使用场景:
- 状态标记量
- double check
- volatile的原理和实现机制:
- 加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障
- 作用:
-
内存屏障
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
- 它会强制将对缓存的修改操作立即写入主存
- 如果是写操作,它会导致其他CPU中对应的缓存行无效
-
原子性、可见性
- 可见性:是指线程之间的可见性,一个线程的修改的状态对另一个线程是可见的,也就是一个线程的修改的结果,另一线程马上能看见
- 原子性:操作不可以中途被cpu暂停然后调度,即不能中断,要不执行完,要不就不执行
-
线程、进程区别
- 一个进程内部可以有多个线程
- 进程拥有独立的地址空间,同一进程下的线程可以共享改内存区域,线程有自己的堆栈和局部变量,但是线程没有独立的地址空间
- 进程是资源分配的基本单位,线程是CPU调度的基本单位
- 调度和切换:线程上下文切换比进程上下文切换要快得多,线程开销相对较小
-
锁的分类以及辨析
- 公平锁/非公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁,非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。
- 可重入锁:可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁(ReentrantLock、synchronized)
- 独享锁/共享锁:独享锁是指该锁一次只能被一个线程所持有(Java ReentrantLock),但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁
- 互斥锁/读写锁:一般的synchronized、ReentrantLock就是互斥锁,ReadWriteLock是读写锁
- 乐观锁/悲观锁:不是指具体的什么类型的锁,而是指看待并发同步的角度,乐观锁(CAS),悲观就是利用各种锁
- 自旋锁/阻塞锁: 自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU(CAS原子操作实现)
- 偏向锁/轻量级锁/重量级锁:这三种锁是指锁的状态,并且是针对Synchronized进行优化的
-
线程池相关
- 优点:
- 减少在创建和销毁线程上所花的时间以及系统资源的开销
- 避免大量的线程间因互相抢占系统资源导致的阻塞现象
- 能够对线程进行简单的管理并提供定时执行、间隔执行等功能
- 基本组成部分:
- 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
- 优点:
-
finalize() 和 system.gc() 的区别
- gc 只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(当使用JNI技术时,可能会在栈上分配内存,例如java调用c程序,而该c程序使用malloc分配内存时).因此,如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行内存回收就要靠finalize()
- finalize的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.
- finalize()在什么时候被调用?
- 所有对象被Garbage Collection时自动调用,比如运行System.gc()的时候
- 程序退出时为每个对象调用一次finalize方法
- 显式的调用finalize方法
- Java程序在调用C++时需要调用finalize()方法来释放内存
-
终止线程的方法
- 使用退出标志Flag,使线程正常退出,也就是当run方法完成后线程终止
- 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
- 使用interrupt方法中断线程
-
IoC思想
- IOC的核心是解耦,解耦的目的是修改耦合对象时不影响另外一个对象…降低模块之间的关联
- 在Android中主要是减少 findviewById(R.id.text)、setXXXListener的操作
- Android提过的框架有Dagger2和butterKnife,底层原理是注解、反射
-
AOP思想
- 面向切面编程,需要把程序逻辑分解成『关注点』(concerns,功能的内聚区域)。这意味着,在 AOP 中,我们不需要显式的修改就可以向代码中添加可执行的代码块。这种编程范式假定『横切关注点』,多处代码中需要的逻辑,但没有一个单独的类来实现)应该只被实现一次,且能够多次注入到需要该逻辑的地方
- 应用场景:日志、持久化、性能监控
- Android中框架:AspectJ、Dexposed