重载与重写的区别?
重载:本类中,方法名相同,参数列表不同,(参数类型、参数顺序、参数个数),返回值类型可以不同,访问修饰符可不同
重写:子类中,方法名相同,参数不能改,返回值类型一致或其子类,访问权限大于等父类
== 与equals区别是什么?
== :
在基本数据类型中 == 比较的是值;在引用数据类型中 == 比较的是地址
equals :
只能比较引用数据类型,默认比较地址,此时等同于 == ;但是String与Ingeter等方法重写了equals方法,变成了值的比较。
final关键字的作用
变量:final修饰的变量必须要初始化,不能再修改
方法:final修饰的方法不能被重写,可以被继承
类:final修饰的类不能被继承
static关键字
- 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
- 用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
- 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;在创建对象时,static修饰的成员会首先被初始化,而且我们还可以看到,如果有多个static修饰的成员,那么会按照他们的先后位置进行初始化
- 静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便。
String、StringBuffer 和 StringBuilder 的区别是什么?
String:
String类中有final修饰,所有String对象不可变。
StringBuffer与StringBuilder:
这俩是继承AbstractBuilder类,AbstractBuilder类没有final,所以是可变的;
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的;
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
抽象类与接口的区别
- 接口中的变量默认用static和final修饰,不能有其他修饰的变量,必须给初始值
- 接口中的方法默认是public abstract,1.8之后可以有static 或者 default 修饰的方法体;
而抽象类中,可以有普通方法和抽象方法,并可以有main方法 - 类可以继承多个接口,但只能实现一个抽象类
- 抽象类可以有构造函数,接口没有
单例设计模式特点及分类(你了解的设计模式有什么(工厂设计模式 代理设计模式 装饰设计模式)
特点:
只能实例化一次对象
只能有本类实例化(构造器私有化)
如果想要调用,对外一定提供一个获取本类对象的方法
分类(线程不一样):
懒汉:多线程 安全
恶汉:单线程 快捷
了解的:
各有优缺点:
- 代理设计模式:为某对象提供一种代理
通过提供代理以控制对该对象的访问。(找一个更熟悉的代理来替我们操作)
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
① 修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
② 采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
- 装饰设计模式:给一个对象增加一些功能,
要求是动态的,并且装饰对象和被装饰对象要实现同一个接口
装饰器模式的应用场景:
① 需要扩展一个类的功能。
② 动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。
缺点:产生过多相似的对象,不易排错
List、Set、Map 之间的区别
- List和Set继承Collection接口;Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同key,每个key只能映射一个value。
- List按对象进入的顺序保存对象,不做排序或编辑操作。List接口存储一组不唯一,可重复(可以有多个元素引用相同的对象),有序的对象。
- Set注重独一无二的性质,不允许重复的集合,对每个对象只接受一次,且无序。
- Map特点:元素按键值对存储,无放入顺序,不可重复。
List接口有三个实现类:LinkedList,ArrayList,Vector
- 同步性:LinkedList与ArrayList都是不同步,不保证线程安全,但效率高;而Vector线程同步,线程安全,但效率低
- 底层数据结构:ArrayList底层使用的是Object数组,支持随机访问;LinkedList底层使用的是双向链表数据结构,不支持随机访问,链表增删快,查找慢。
- 数据增长:ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。即Vector增长原来的一倍,ArrayList增加原来的0.5倍。
Set接口有两个实现类:HashSet,LinkedHashSet
HashSet:
为快速查找设计的Set。存入HashSet的对象必须定义hashCode()
- 底层由HashMap实现
- HashSet的值存放在HashMap的key上
- HashMap的value统一为PRESENT
LinkedHashSet:
具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
Map接口有三个实现类:HashMap,HashTable,LinkeHashMap
HashMap:
HashMap是Hashtable的轻量级实现(非线程安全的实现)非线程安全,高效,支持null键值(key)
HashTable:
HashTable线程安全,低效,不支持null
LinkeHashMap:
类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。
Java中实现多线程的三种方式
通过继承Thread类:
创建Thread的子类来继承Thread类,并重写run()方法,在测试类中调用start()方法开启多线程
通过Runnable接口创建:
创建Runnable接口的实现类,重写run()方法,并通过Thread类构造器建立关联,通过关联对象调用start()方法开启多线程
通过Callable和Future创建:
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值;
使用FutureTask对象作为Thread对象的target创建并启动新线程;调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
两种方式实现多线程,哪一种更好(如何开启多线程?方式区别?)
如何开启多线程:
无论是继承,还是实现接口,都是调用Thread类中的start方法开启
方式区别:
相同点:无论是继承Thread类,还是实现Runnable接口,都是将多线程执行代码定义在run方法中,并都是通过start方法开启多线程操作
不同点:实现Runnable接口需要增加一步与Thread建立关联的操作,然后才能调用start方法
实际开发中,推荐使用Runnable接口方式,避免Java中单继承的局限性
线程有哪些基本状态?
图源《Java 并发编程艺术》4.1.4节
线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
sleep() 和 wait() 有什么区别?
- sleep() 方法执行完成后,线程会自动苏醒,或者可以使用 wait(long timeout)超时后线程会自动苏醒;线程类(Thread)的静态方法,通过Thread打点调用;不能改变对象的机锁,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法,我们将以上操作称之为等待唤醒机制;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问
补充
notify唤醒线程池中先进入的
notifyAll唤醒线程池中所有的线程,然后这些被唤醒的线程再次“抢”占CPU执行权;哪个线程“抢”到,哪个就执行;而没抢到的再次回到线程池中;因此,有的线程可能刚被唤醒,就又等待了
什么是线程死锁?
两个或两个以上线程因竞争资源,而陷入彼此相互等待,无外力作用下永远等待下去的现象
产生死锁的必要条件
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求时,因请求资源而阻塞,但对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁?
为了避免死锁,我们只要破坏产生死锁的四个条件中的其中一个就可以了。
- 破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
- 破坏请求与保持条件:一次性申请所有的资源。
- 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。