Object
- equals方法:如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址,而String、Date等类对equals方法进行了重写,比较的是所指向的对象的内容
- == :如果作用于基本数据类型的变量,则直接比较其存储的值是否相等,如果作用于引用类型的变量,则比较的是所指向的对象的地址
- hashCode方法:给对象返回一个hash code值
- 同一个对象多次调用hashCode方法,始终返回同一个值
- 如果两个对象用equals方法判断相等,则二者的hashCode方法返回同样的值
- 如果两个对象的hashCode方法返回同样的值,这两个对象也不一定相等
- 如果两个对象不相等,则hashCode返回的值一定不等
static
- 只要类被加载了,static关键字修饰的方法或者变量就可以通过类名去访问,而不需要类进行实例化
- 静态变量被所有对象共享,在内存中只有一个副本,当且仅当类初次加载时被初始化
- 只要访问权限允许,所有静态方法和静态变量也可以通过对象访问
- 局部变量不可以用static修饰
final
- 可以声明成员变量、方法、类以及本地变量
- final成员变量必须在声明的时候初始化或者在构造器中初始化
- fianl变量是只读的
- final方法不可以重写
- fianl类不可以继承
- final变量可以在多线程环境下共享
- final关键词提高性能
- 如果方法的内部类想访问方法中的局部变量,则需要用final修饰
String、StringBuffer、StringBuilder的区别
- String是final类,不能被继承,因此对于已存在的String对象进行修改,需要重新创建一个对象
- StringBuffer类似于String的字符串缓冲区,修改值不需要重新创建对象,并且是线程安全的
- StringBuilder用来代替StringBuffer,是非线程安全的,速度更快
异常处理
- Exception、Error是Throwable类的子类
- Error类对象由java虚拟机生成并抛出,不可进行捕捉
- 不管有没有异常,finally中的代码都会执行,即使try/catch中存在return也如此
- 常见的Error
- OutOfMemoryError
- StackOverflowError
- NoClassDeffoundError
- 常见的Exception
- 非检查型异常
- ArithmeticException
- ArrayIndexOutOfBoundsException
- ClassCastException
- IllegalArgumentException
- IndexOutOfBoundsException
- NullPointerException
- NumberFormatException
- SecurityException
- UnsupportedOperationException
- 检查性异常
- IOException
- CloneNotSupportedException
- IllegalAccessException
- NoSuchFieldException
- NoSuchMethodException
- FileNotFoundException
- 非检查型异常
内部类
- 非静态内部类没法在外部类的静态方法中实例化
- 非静态内部类的方法可以直接访问外部类的所有数据,包括私有的数据
- 在静态内部类中调用外部类的成员,成员必须是static修饰的
- 创建静态内部类的对象可以直接通过外部类名调用静态内部类的构造方法
- 创建非静态内部类的对象必须先创建外部类的对象,然后通过外部类的对象调用内部类的构造方法
- 匿名内部类不能定义任何的静态成员、方法
- 匿名内部类中的方法不能是抽象的
- 匿名内部类必须实现接口或者抽象父类的所有抽象方法
- 匿名内部类不能定义构造器
- 匿名内部类访问外部类成员必须用final修饰
多态
- 父类的引用可以指向子类的对象
- 创建子类对象时,调用的方法为子类重写的方法或者继承的方法
- 如果子类中编写了一个子类独有的方法,则无法通过父类引用创建的子类对象去调用该方法
抽象和接口
- 抽象类不能实例化
- 抽象类的抽象方法必须在子类中被重写
- 接口中所有属性默认为:public static final
- 接口中所有的方法默认为:public abstract
集合框架
- HashMap
- JDK 1.7以前的HashMap是以数组加链表的形式存储数据,JDK 1.8以后的HashMap,如果链表长度大于8,则使用红黑树替换链表来存储数据
- put过程:调用对象的hashCode方法获取hash code,然后找到map中相应的位置存放该对象,如果该位置已经存在一个对象,则会发生哈希碰撞,该对象保存在该位置的链表中
- get过程:通过键对象的equals方法找到正确的键值对,返回值对象
- 如果HashMap的大小超过了负载因子定义的容量,则会创建比原来的HashMap的数组大两倍的数组,并将原来的对象重新hash存入新的数组中
- String、Interger等封装类更适合作为键,因为它们都是final修饰的,可以防止键值改变,而且已经重写了equals方法和hashCode方法,可以减少碰撞,提高HashMap的性能
- HashMap和HashTable对比
- HashMap是非线程安全的,性能更好,可以接受null的key-value
- HashTable是线程安全的,速度较慢,不能接受null的key-value
单例
-
懒汉式:时间换空间
-
非线程安全懒汉式
public class Singleton{ private static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
-
双重检查线程安全懒汉式
public class Singletion{ private static volatile Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
-
-
饿汉式:空间换时间,线程安全
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } }
-
破坏单例模式的三种方式
- 反射
- 序列化
- 克隆
-
解决方案
-
防止反射:定义一个全局变量,当第二次创建的时候抛出异常
-
防止克隆:重写clone方法,直接返回单例对象
-
防止序列化:添加readResolve方法,返回Object对象
public class Singleton implements Serializable,Cloneable{ private static volatile boolean isCreate = false;//默认是首次创建 private static volatile Singleton instance; private Singleton(){ if(isCreate){ throw new RuntimeException("无法重复实例化该对象"); } isCreate = true; } public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } @Override protected Object clone() throws CloneNotSupportedException{ return instance; } private Object readResolve(){ return instance; } }
-
线程
-
线程是进程中可独立执行的最小单位,也是CPU资源(时间片)分配的基本单位
-
线程状态
-
New:新创建了一个线程对象,但还没有调用start方法
-
Runnable
- Ready:等待被线程调度选中,获取cpu的使用权
- Running:获得了cpu时间片
-
Timed Waiting:有等待时间的等待状态
-
Waiting:进入等待状态,因为调用了以下方法
- Object#wait()
- Thread#join()
- LockSupport#park()
-
Blokced:线程因为某种原因放弃了cpu的使用权,暂时停止运行
-
Terminated:表示该线程已经执行完毕
-
状态控制
- wait():释放当前锁,让出CPU,并将当前线程放入等待队列中
- notify():将等待队列中的一个等待线程从等待队列移动到同步队列中
- notifyAll():将所有等待队列中的线程移动到同步队列中
- 唤醒等待队列的线程后,当前线程不会马上释放对象锁,处于等待状态的线程也无法马上获取该对象锁,要等程序退出同步块或者同步方法后,当前线程才会释放锁,处于等待状态的线程才能获取锁
- join():父线程等待子线程执行完毕后继续执行,等待过程中会释放锁
- sleep():该方法在睡眠时不释放对象锁
- yield():临时暂停当前正在执行的线程,但也不会释放对象锁,让有同样优先级的正在等待的线程有机会执行,如果没有此类优先级的线程,则该线程继续运行
Volatile
- 可见性:指线程之间的可见性,一个线程修改的状态对另一个线程可见
- 原子性:最小单位,具有不可分割性
- 有序性:线程之间操作的有序性
- 当把变量声明为volatile类型后,编译器和运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序,volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此在读取volatile类型变量的时候总会返回重新写入的值
- 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制
- 当非volatile变量进行读写时,每个线程先从内存拷贝变量到cpu缓存中,如果计算机有多个cpu,每个线程可能在不同的cpu上被处理,这就意味着每个线程可以拷贝到不同的cpu cache中。而声明为volatile变量后,每次读取变量都是从内存中读,跳过了cpu cache这一项
- 当一个变量定义为volatile后,将具备两种特性:
- 该变量对所有线程可见
- 禁止指令重新排序,利用内存屏障
- 内存屏障:指令排序时不能把后面的指令重排序到内存屏障之前的位置
- 指令重排序:指CPU采用了允许将多条指令不按程序规定的顺序分开发送给相应的电路单元处理
synchronized
- 用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只能有一个线程执行该段代码
- 在java中,每个对象都会有一个monitor对象,这个对象其实就是java对象的锁,称为“内置锁”或者“对象锁”,每个类也有一个锁,称为“类锁”
- 对象锁
- synchronized(this|object){}
- 修饰非静态方法
- 类锁
- synchronized(Class.class){}
- 修饰静态方法
- 悲观锁:认为自己在使用数据的时候一定会有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。悲观锁适合写操作多的场景,synchronized和lock的实现都是悲观锁
- 乐观锁:认为自己在使用数据的时候不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据,如果没更新,则当前线程将自己修改的数据成功写入,如果已更新,则根据不同的实现方式执行不同的操作,例如报错或者自动重试。最常采用CAS算法,适合读操作多的场景
- 死锁:多个线程相互持有对方所需资源,并且都不主动释放所占有资源,而是都处于等待状态,无法向前执行
- 死锁产生条件
- 互斥条件:一个资源只能被一个线程持有,直到该线程主动释放
- 请求和保持条件:一个线程因请求被别的线程占有的资源而发生阻塞时,对已获得的资源保持不释放
- 不剥夺条件:其他线程无法对持有资源的线程进行剥夺
- 循环等待条件:当发生死锁时,所等待的线程必定形成一个环路死循环,造成永久阻塞
- 避免死锁:破坏死锁产生条件的其中一条
引用类型
- 强引用(StrongReference):垃圾回收器绝对不会对其进行回收销毁,非静态内部类会在其整个生命周期中持有对它外部类的强引用
- 弱引用(WeakReference):在垃圾回收器执行的时候,该对象会被回收
- 软引用(SoftReference):只有在内存空间不足的情况下才对其进行回收
持不释放- 不剥夺条件:其他线程无法对持有资源的线程进行剥夺
- 循环等待条件:当发生死锁时,所等待的线程必定形成一个环路死循环,造成永久阻塞
- 避免死锁:破坏死锁产生条件的其中一条
引用类型
- 强引用(StrongReference):垃圾回收器绝对不会对其进行回收销毁,非静态内部类会在其整个生命周期中持有对它外部类的强引用
- 弱引用(WeakReference):在垃圾回收器执行的时候,该对象会被回收
- 软引用(SoftReference):只有在内存空间不足的情况下才对其进行回收
- 虚引用(PhantomReference):任何时候都会被回收,对对象的生命周期没有影响,仅仅能在对象被回收时,得到一个系统通知(只能通过是否被加入ReferenceQueue来判断是否被GC,这也是唯一判断对象是否被GC的途径)