面向对象
面向对象是指将数据以及对数据的操作方法放在一起,作为一个相互依赖的整体,即对象。对同类对象抽象出共性,叫做类。
面向对象的特点抽象、封装、继承、多态
继承
子类继承父类非私有的成员变量和方法,提高代码的复用性,提高开发效率。
继承通过extends关键字实现。
java不支持多重继承
当子类中定义的成员变量或者成员方法和父类的相同时,子类将覆盖父类的成员变量或方法,而不会继承。
多态
多态指同一个事件发生在不同的对象上会产生不同的结果。
多态的三个必要条件:继承,重写,向上转型(子类的引用赋给父类的对象)
java提供了编译时多态和运行时多态两种多态机制。编译时多态通过方法重载实现,运行时多态通过方法重写实现。
注意:成员变量没有多态的概念,所以有“变量看左,方法看右”的说法。
重载
重载指同一个类里有多个同名的方法,同名方法有不同的参数,所以编译时就知道要调用哪个方法。
重写
重写即子类覆盖父类的方法,使用时 父类或者接口的引用变量指向子类或者实现类的实例对象。程序在运行时才动态绑定引用变量和实例对象。
抽象类和接口的异同
相同点:
都不能被实例化
接口的实现类和抽象类的子类都只有实现了接口或者抽象类的方法才可被实例化
不同点
抽象类 | 接口 | |
---|---|---|
声明 | abstract | interface |
继承 | 单继承 | 多继承 |
实现 | extends | implements |
子类 | 可以实现部分抽象方法,改类还是抽象类 | 必须实现所有方法 |
方法 | 有抽象方法的类就行抽象类;抽象类中可以有非抽象方法 ;抽象方法不可以有方法体 | 类中方法不可以有方法体 |
变量 | all ,默认是default | 默认且只能是public static final类型 |
方法修饰 | 抽象方法不能被private、static、synchronized、native等访问修饰符修饰 | 只能用public 和abstract |
使用 | 充当公共类的角色 | 实现常用功能 |
构造器 | 有 | 没有 |
this 和super 的区别
this :用来指向当前类的实例对象
super:用来访问父类的方法或者成员变量
final
被final声明的属性不可变、方法不可覆盖,类不能被继承
static 关键字
static 关键字的作用是:1.为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关
2.在不创建对象的情况下可以通过类来直接调用方法或者使用类的属性
java 的static主要有4种使用情况:成员变量、成员方法、代码块和内部类
static 成员变量:可以通过static 关键字达到全局变量的作用
static 方法:不需要创建对象就可以被调用
instanceof
判断对象是否属于某类
StringBuffer sb = new StringBuffer();
if(sb instanceof StringBuffer){
System.out.println(true);
}
变量的类型及作用范围
成员变量:成员变量的作用范围和类实例化的作用范围一致
静态变量:被所有的实例共享,只要类一旦被加载,就会给类的静态变量分配内存空间
局部变量:作用域为所在的花括号内
构造函数
用来在对象实例化时初始化对象的成员,主要是完成对象的初始化工作
构造函数的特点:
1)构造函数的名称必须和类名相同,没有返回值
2)一个类的构造函数可以有一个或者多个
3)构造函数的参数可以有0个一个或者一个以上
4)构造函数不能被继承,因此不能被重写,但是可以被重载
public class AAA {
private int a;
public AAA(int a){
this.a=a;
}
}
package的作用
1.提供多层的命名空间,解决命名的冲突
2.对类按功能进行分类,使得项目的组织更加清晰
java的权限修饰符
1.public :对所有的类和对象都是可见的
2.Private :只在当前类具有访问权限
3.Protected:该类本身,同一包下的其他的类,以及其他包下该类的子类都可见
4.Default :该类以及同一包下的其他类
类加载过程
加载->链接->初始化->使用->卸载
链接 = 验证+准备+解析
1.加载
通过类的全限定名查找到该类的字节码文件,将该字节码文件装载到jvm中,jvm将文件中静态字节码结构转换成
运行时动态数据结构,并在方法区生成一个定义该类的Class对象,作为方法区中该类的各种数据访问的入口。
2.验证
确保该类的字节码文件中所包含的信息是否符合当前虚拟机的要求,不包含有危害虚拟机的信息(主要有四种验证,
文件格式验证,元数据验证排(语义)、字节码验证(防止危害虚拟机),符号引用验证)
3.准备
为类变量分配内存,并设置一个初始值。被final修饰的类变量,该类型会在编译期就已经被分配并确定
4.解析
将常量池中符号间接引用替换成直接引用
5.初始化
为类变量、静态代码块进行真正初始化(赋值操作)(类的初始化顺序,如果有父类先初始化父类中类变量
和静态代码块,在初始化子类的静态变量、静态代码块’)
类加载顺序
先执行父类的静态代码块和静态变量初始化,静态代码块和静态变量的执行顺序跟代码中出现的顺序有关。
执行子类的静态代码块和静态变量初始化。
执行父类的实例变量初始化
执行父类的构造函数
执行子类的实例变量初始化
执行子类的构造函数
内部类
定义在类内部的类
主要有静态内部类、成员(普通)内部类、局部内部类、匿名内部类
基本类型
类型 | 说明 | 位 | 字节 | 取值范围 | 默认值 |
---|---|---|---|---|---|
byte: | Java中最小的数据类型, | 在内存中占8位(bit), | 即1个字节, | 取值范围-128~127, | 默认值0 |
short: | 短整型, | 在内存中占16位, | 即2个字节, | 取值范围-32768~32717, | 默认值0 |
int: | 整型,用于存储整数, | 在内在中占32位, | 即4个字节, | 取值范围-2147483648~2147483647, | 默认值0 |
long: | 长整型, | 在内存中占64位, | 即8个字节 | -263~263-1, | 默认值0L |
float: | 浮点型, | 在内存中占32位, | 即4个字节,用于存储带小数点的数字(与double的区别在于float类型有效小数点只有6~7位), | 默认值0 | |
double: | 双精度浮点型,用于存储带有小数点的数字, | 在内存中占64位, | 即8个字节, | 默认值0 | |
char: | 字符型,用于存储单个字符, | 占16位, | 即2个字节, | 取值范围0~65535, | 默认值为空 |
boolean: | 布尔类型, | 占1个字节, | 用于判断真或假(仅有两个值,即true、false), | 默认值false | |
null |
基本类型放在栈中,直接存储值。
所有数值类型都有正负号,没有无符号的数值类型。
基本数据类型有固定的存储范围和所占内存空间的大小,而不受具体操作系统的影响,来保证Java程序的可移植性。
整形数据默认为int数据类型,浮点型默认为double数据类型,如果要表示long型数据或float型数据,要在相应的数值后面加上l、L或f、F,否则会出现编译问题。
基本数据类型间的转换
1、boolean类型不能转换成任何其他数据类型。
2、自动类型转换:容量小的数据类型可以自动转换成容量大的数据类型,如byte-short-int-long-float-double。
byte、short、int不会互相转换,他们三者在计算时会转换成int类型。
3、强制类型转换:容量大的数据类型转换成容量小的数据类型时,要加上强制转换符,但这样有可能会造成精度降低或者数据溢出,要小心。
包装类
Java为每个基本类型都提供了相应的包装类,这样便能将基本类型转化为对象来处理
JDK 5.0为基本数据类型提供了自动装箱(boxing)和拆箱(unboxing)的功能。
装箱:将基本数据类型包装秤对应的包装类对象。
拆箱:将包装类对象转换成对应的基本数据类型
引用类型
主要包含三类:类,接口,数组
类包括Object 类、String 类
引用类型 | 对象是否可引用 | 回收时间 | 使用场景 |
---|---|---|---|
强引用 | 可以 | 从不回收 | 普遍对象的状态 |
软引用 | 可以 | 内存不足时 | 内存敏感的高速缓存 |
弱引用 | 可以 | 下一次GC | 对象缓存 |
虚引用 | 不可以 | 下一次GC | ,不影响对象生命周期 必须和引用队列(ReferenceQueue)一起使用,一般用于追踪垃圾收集器的回收动作。相比对象的finalize方法,虚引用的方式更加灵活和安全。 |
int和Integer的区别
1.int是基本数据类型,Integer是int的封装类,是引用类型。
5、Integer 变量必须实例化后才能使用,int变量不需要
2、int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。
3.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值
4、引用类型在堆里,基本类型在栈里。
值传递和引用传递
值传递: 在方法调用中,实参会把值传递给形参,形参和实参是不同的存储单元,对形参的改变不会影响实参
引用传递:传递的是对象的地址,这时形参与实参的对象指向同一块存储单元,因此对形参的改变会影响实参
Java:原始的数据类型都是值传递,包装类是引用传递
“==”、 equal、 hashCode有什么区别
1)== 用来比较两个变量的值是否相等。如果变量指向的是基本数据类型,那比较的是值是否相等;如果变量指向的是引用数据类型,那比较的是两个变量的引用地址。
2)equal()是Object类提供的方法,底层通过= = 实现,但是对象可以重写equal方法。String类重写了equal方法,用来比较两个对象的值是否相等。
3)hashCode()也是Object类提供的方法,返回对象在内存中地址转换成的一个int值,所以如果没有重写hashCode()方法,任何对象的hashCode()返回的值都是不相等的。
equal()和hashCode()都用来判断两个对象是否相等,但是equal()是给用户调用的。如果要判断两个对象是否相等,可以重写equal()方法。用户一般不会调用hashCode()方法,比如hashMap中,判断key是否重复就是判断hashCode()和equals()方法。
一般重写equal()方法时也要覆盖hashcode()方法,否则该类无法和所有基于散列值(hash)的集合类(hashMap hashSet hashTable)结合正常运行。
String方法
public class AAA {
public static void main(String[] args) throws Exception {
String s = "asd";
// 切割字符串
String[] nums = s.split("s");
Arrays.stream(nums).forEach(item -> System.out.println(item));
// 替换字符
String m = s.replace("s", "");
System.out.println(m);
// 字符串重写了equals方法
System.out.println(s.equals("asd"));
System.out.println(s.charAt(2)); //
// 字符串转数字
char[] array = s.toCharArray();
System.out.println(array[1]);
// 取子字符串 [)
System.out.println(s.substring(0,2));
// 字符串长度
System.out.println(s.length());
// 包含子字符
System.out.println(s.contains("s"));
// 以**结束
System.out.println(s.endsWith("d"));
// 连接字符串
String r = s.concat("qa");
System.out.println(r);
// 字符串起始位置
System.out.println(s.indexOf("as"));
// 判断是否为空
System.out.println(s.isEmpty());
// 转小写
System.out.println(s.toLowerCase());
// 转大写
System.out.println(s.toUpperCase());
}
}
StringBuffer方法
public static void main(String[] args) throws Exception {
StringBuffer sb = new StringBuffer();
// 添加字符串
sb.append("sdas");
// 替换 【)
sb.replace(1,2,"qq");
System.out.println(sb.toString());
// 插入
sb.insert(1,"hg");
System.out.println(sb.toString());
// 删除 [)
sb.delete(1,2);
System.out.println(sb.toString());
// 反转
sb.reverse();
System.out.println(sb.toString());
// 子串
String m = sb.substring(2);
System.out.println(m);
}
String ,StringBuffer,StringBuilder 的区别
1、String 是不可变类 ,StringBuffer 和StringBuilder 可变类
2、StringBuffer 和StringBuilder都是字符串缓存区,StringBuilder 线程不安全,单线程可以使用;StringBuffer 线程安全,适合多线程使用
String 存储机制
String s = "sas"; // 直接引用常量池中的字符串
String m = "sas";
System.out.println(s==m); // true
String n = new String("sas"); // 先创建对象,对象引用常量池中的字符串
System.out.println(m==n); // false
数组操作
数组是一组具有相同类型的数据集合,有固定的长度,并且在内存中具有连续的存储空间
String[] nums = {"121","454","323"};
//获取数组的长度
System.out.println(nums.length);
// 数组排序
Arrays.sort(nums);
Arrays.stream(nums).forEach(item-> System.out.println(item));
// 数组转list
List<String> list = Arrays.asList(nums);
list.stream().forEach(item-> System.out.println(item));
Vector、ArrayList、LinkedList的区别
三个均为可伸缩数组,即可动态改变长度的数组
Vector、ArrayList以数组的形式存储在内存中,LinkedList采用双向列表实现
Vector线程同步,ArrayList、LinkedList线程不同步。
LinkedList适合指定位置插入、删除操作,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操作。
List 和set的区别
List中的元素有序、允许有重复的元素,Set中的元素无序、不允许有重复元素。
hashMap 、hashtable、TreeMap的区别
java为数据结构中的映射定义了一个接口java.util.Map,Map是将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射一个值。
HashMap、 Hashtable和TreeMap是Map的实现类
Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力.
Hashtable 与 HashMap类似,但是主要有6点不同。
1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
2.HashTable不允许null值,key和value都不可以,HashMap允许null值,key和value都可以。HashMap允许key值只能由一个null值,因为hashmap如果key值相同,新的key, value将替代旧的。
3.HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
4.HashTable使用Enumeration,HashMap使用Iterator。
5.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
6.哈希值的使用不同,HashTable直接使用对象的hashCode。
TreeMap继承了SortMap的接口,取出来的是排序的键值对,
如果需要输入和输出的顺序相同,用LinkedHashMap可以实现
进程和线程
进程是资源分配的基本单位,线程是CPU调度的基本单位
一个进程可以有多个线程,多个线程之间共享程序的内存资源(代码段,数据的和堆空间)
每个线程都有独立的栈空间
线程的创建和切换的开销更小。使用多线程可以减少程序的响应时间;多CPU或多核计算机本身就具备执行多线程的能力;
在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。
线程状态
线程状态:就绪、运行、挂起/阻塞、结束
如何创建多线程
1.继承Thread 类,重写run方法
2.实现Runnable接口,并实现该接口的run()方法
3.实现Callable接口,重写call()方法
(1)Callable可以在任务结束后提供返回值,Runnable不可以
(2)Callable中的call()方法可以抛出异常,Runnable的run不可抛出异常
(3)运行Callable可以拿到Future对象,Future对象表示异步计算的结果。
run()和start()的区别
系统通过调用线程类的start()方法来启动一个线程,此时该线程处于就绪状态,而非运行状态。就绪状态意味着这个线程可以被JVM来调度执行。线程执行时,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后 此线程就终止。
并行和并发的区别是什么
•并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生
• 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件
为何要使用同步
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
线程同步方法
同步的实现机制是锁
1.同步方法
即由synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
public synchronized void save(){}
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
使用在非静态方法上就是对象锁
public class AAA {
public static void main(String[] args) throws Exception {
B b = new B();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(b);
thread.start();
}
}
}
class B implements Runnable {
public static int account = 100;
@Override
public void run() {
getLen2();
}
public static synchronized void getLen2() {
account += 200;
System.out.println(account);
}
}
2.同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
public class AAA {
public static void main(String[] args) throws Exception {
B b = new B();
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(b);
thread.start();
}
}
}
class B implements Runnable {
private int tickets = 5;
@Override
public void run() {
//在同一个时间片刻 只能有一个线程来访问这块共享资源
//只有当访问中的线程离开了,下一个线程才能够访问这块共享资源
synchronized (this) {//同步线程锁
while (true) {//进入死循环
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "出票号:"+tickets -- );
}
}
}
}
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
**3、wait() notify() **
在这里插入代码片
4、使用重用锁
public class AAA {
public static void main(String[] args) throws Exception {
B b = new B();
new Thread(b, "线1").start();
new Thread(b, "线2").start();
new Thread(b, "线3").start();
}
}
class B implements Runnable {
private int ticket = 10; // 多个线程同时访问ticket变量,比如三个窗口一共售10张票
private Lock lock = new ReentrantLock();
public void run() {
for (int i = 0; i < 200; i++) {
lock.lock();
try {
if (ticket >= 1) {
Thread.sleep(20);
System.out.println(Thread.currentThread().getName() + "获取" + ticket-- + "号");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
5、redis实现
public class Synchrolock {
@Resource
private RedisUtils redisUtils;
/**
* 根据传入key值,判断缓存中是否存在该key
* 存在-已上锁:判断retryType,轮询超时,或直接返回,返回ture
* 不存在-未上锁:将该放入缓存,返回false
*
* @param key
* @param retryType
* @param retryInterval 同步锁轮询间隔 毫秒
* @param requestTimeOut 同步锁请求超时时间 毫秒
* @param keyTimeout 缓存中key的失效时间 秒
* @return false:未加锁获取锁,true:已加锁
*/
public boolean islocked(String key, String retryType, Long retryInterval, Long requestTimeOut,
Integer keyTimeout, Long startTime, String uuid) {
boolean flag = true;
log.info("====同步锁设置key:{},轮询方式retryType:{},轮询间隔retryInterval:{}、请求超时时长requestTimeOut:{}、" +
"缓存key失效时长keyTimeout:{},开始时间startTime:{},值uuid:{} ====", key, retryType, retryInterval,
requestTimeOut, keyTimeout, startTime, uuid);
//调用缓存获取当前产品锁
log.info("====当前key为:" + key + "====");
if (isLockedInRedis(key, keyTimeout,uuid)) {
if (Constants.RETRYTYPE_WAIT.equals(retryType)) {
//采用轮询方式等待
while (true) {
log.info("====已被占用,开始轮询====");
try {
Thread.sleep(retryInterval);
} catch (InterruptedException e) {
log.error("线程睡眠异常:" + e.getMessage(), e);
return flag;
}
log.info("====判断请求是否超时====");
Long currentTime = System.currentTimeMillis(); //当前调用时间
long Interval = currentTime - startTime;
if (Interval > requestTimeOut) {
log.info("====请求超时====");
return flag;
}
if (!isLockedInRedis(key, keyTimeout, uuid)) {
log.info("====轮询结束,添加同步锁====");
flag = false;
break;
}
}
} else {
//不等待,直接返回
log.info("====已被占用,直接返回====");
return flag;
}
} else {
log.info("====未被占用,添加同步锁====");
flag = false;
}
return flag;
}
/**
* 在缓存中查询key是否存在
* 若存在则返回true;
* 若不存在则将key放入缓存,设置过期时间,返回false
*
* @param key
* @param keyTimeout key超时时间单位是秒
* @return
*/
boolean isLockedInRedis(String key, int keyTimeout, String uuid) {
log.info("====在缓存中查询key:{}是否存在====", key);
boolean isExist = false;
//与redis交互,查询对象是否上锁
Boolean result = redisUtils.set(key, uuid);
log.info("====上锁 result = " + result + "====");
if (null != result && true == result) {
log.info("====设置缓存失效时长 = " + keyTimeout + "秒====");
redisUtils.expire(key, keyTimeout);
log.info("====上锁成功====");
isExist = false;
} else {
log.info("====上锁失败====");
isExist = true;
}
return isExist;
}
/**
* 根据传入key,对该产品进行解锁
* @param redisKey
* @param redisValue
*/
public void unlock(String redisKey, String redisValue) {
//与redis交互,对产品解锁
Object value = redisUtils.get(redisKey);
if (null != value && !"".equals(value.toString())) {
if (value.equals(redisValue)) {
log.info("====解锁key:" + redisKey + " value=" + value + "====");
redisUtils.del(redisKey);
} else {
log.info("====待解锁集合中key:" + redisKey + " value=" + value + "与uuid不匹配====");
}
} else {
log.info("====待解锁集合中key=" + redisKey + "的value为空====");
}
}
}
Sleep 和wait 的区别
1、sleep是Thread类的方法,wait是Object类中定义的方法
2、 sleep 让线程从 【running】 -> 【阻塞态】 时间结束/interrupt -> 【runnable】
wait 让线程从 【running】 -> 【等待队列】notify -> 【锁池】 -> 【runnable】
3、sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
死锁
死锁就是两个或者两个以上的进程在执行过程中因互相竞争资源,而造成互相等待的现象,如果没有外力作用,死锁涉及到的各个进程都将永远处于封锁状态。
死锁
死锁发生的必要条件
互斥条件(Mutual exclusion ):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait ):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption ):已经分配的资源不能从相应的进程中被强制地剥夺。
环路等待条件(Circular wait ):系统中若干进程组成环路,改环路中每个进程都在等待相邻进程正占用的资源。
线程池
并发执行时需创建大量的线程,频繁的创建线程会降低系统的性能,因为频繁创建线程和销毁线程需要时间。
线程池使线程可以重复被使用,线程池中的线程执行完一个任务不会被销毁,而是可以继续执行其他的任务。
ThreadPoolExecutor类是线程池中最核心的一个类
ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
1、corePoolSize : 代表核心池的大小。默认线程池中没有线程,有任务时才创建线程去执行任务。调用prestartAllCoreThreads()方法可以创建corePoolSize个线程;调用prestartCoreThread()方法可以创建一个线程。在任务执行时,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
2、maximumPoolSize:表示在线程池中最多能创建多少个线程
3、keepAliveTime:线程池中的线程数达到corePoolSize个时,如果一个线程空闲的时间达到keepAliveTime就会终止。当池中的线程数小于corePoolSize时,keepAliveTime不会生效。如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
4、unit:参数keepAliveTime的时间单位,有7种取值
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5、workQueue:一个阻塞队列,用来存储等待执行的任务
ArrayBlockingQueue;基于数组的先进先出队列,此队列创建时必须指定大小;
LinkedBlockingQueue; 常使用;基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
SynchronousQueue;这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
6、threadFactory 线程工厂,用来创建线程
7、handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
setCorePoolSize:设置核心池大小
setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
什么是反射机制
一、Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。
二、反射提供的方法:在运行时判定任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判定任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理;
三、反射最常用的
· 根据类名创建实例(类名可以从配置文件读取,不用new,达到解耦)
· 用Method.invoke执行方法
四、主要的反射机制类:
java.lang.Class; //类
java.lang.reflect.Constructor;//构造方法
java.lang.reflect.Field; //类的成员变量
java.lang.reflect.Method;//类的方法
java.lang.reflect.Modifier;//访问权限
示例:
package com.sxt;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AAA {
public static void main(String[] args) throws Exception{
// 获取class类
Class aClass = null;
// 方法一:
aClass = Refle.class;
// 方法二:
aClass = Class.forName("com.sxt.Refle");
// 方法三:
Refle rf = new Refle();
aClass = rf.getClass();
// 方法四
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class ref = classLoader.loadClass("com.sxt.Refle");
// 获取字段
Field[] fields = aClass.getFields();
for(int i =0;i<fields.length; i++){
System.out.println(fields[i].getName());
}
Method[] methods = aClass.getMethods();
for(int i =0;i<methods.length; i++){
System.out.println(methods[i].getName());
}
// 创建对象
Refle s = (Refle) ref.newInstance();
s.test();
// 执行对象方法
Method med = ref.getDeclaredMethod("test");
med.invoke(rr);
}
}
class Refle{
public Integer a;
public String test(){
return "aaa";
}
}
Class.forName和ClassLoader的区别
class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。当然还可以指定是否执行静态块。
classLoader将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
java创建对象的四种方法
1、new
2、使用clone
3、序列化
4、反射
java数据结构
ArrayList方法
增:add addAll
删:remove remoceAll clear removeRange removeIf
改:set replaceAll
查:get contains containsAll size isEmpty
操作:subList sort toArray toString indexOf lastIndexOf
(栈队列也可以直接用list,只操作相应位置即可)
Set方法
增:add addAll
删:remove remoceAll retainAll clear
改:
查:contains containsAll size isEmpty
操作:toArray iterator equals
Collections方法
Collections是针对Collection集合类的包装类,不包含Map相关的方法
public static void main(String[] args) throws Exception {
List<String> l = new ArrayList<>();
l.add("qwqw");
l.add("sad");
l.add("cx");
// 判断list是否为空
if(l.isEmpty()){
// 返回空list,不可进行操作,map,set类似
// new ArrayList<>()创建时有初始大小,占用内存;emptyList()不会创建新对象,可减少内存开销
// emptyList()不会报空指针异常;如果返回null,没有进行非空判断就会报空指针异常;
Collections.emptyList();
Collections.emptyMap();
Collections.emptySet();
}
// 反转list
Collections.reverse(l);
l.stream().forEach(item->{
System.out.println(item);
});
// list排序
Collections.sort(l);
l.stream().forEach(item->{
System.out.println(item);
});
// list拷贝,需要指定原数组的大小
List<String> q = new ArrayList<>(Arrays.asList(new String[l.size()]));
Collections.copy(q, l);
q.stream().forEach(item->{
System.out.println(item);
});
// 集合中最小
System.out.println(Collections.min((l)));
}
Iterator
迭代器,遍历序列中的对象
将对象转为迭代器,并操作对象
使用Iterator不可在遍历容器的同时对容器进行增删操作
List<String> l = new ArrayList<>();
l.add("qwqw");
l.add("sad");
l.add("cx");
Iterator<String> it = l.iterator();
//
while (it.hasNext()) {
String ss = it.next();
System.out.println(ss); // qwqw sad cx
if (ss.equals("cx")) {
it.remove();// 删除元素
}
}
l.stream().forEach(item -> System.out.println(item)); // qwqw sad
ListIterator
1、Iterator只能正向遍历,ListIterator可双向遍历
2、ListIterator继承自Iterator
3、Iterator遍历时不可修改元素,ListIterator可以修改元素
4、ListIterator只对List有效
List<String> l = new ArrayList<>();
l.add("qwqw");
l.add("sad");
l.add("cx");
ListIterator<String> it = l.listIterator();
while (it.hasNext()) {
String ss = it.next();
System.out.println(ss); // qwqw sad cx
if (ss.equals("cx")) {
it.remove();// 删除元素
it.add("mmm");
}
}
l.stream().forEach(item -> System.out.println(item)); // qwqw sad mmm
ArrayList和LinkedList的区别?
ArrayList和LinkedList都是非线程安全的、可改变长度的数组。
1、ArrayList是基于Object[] arrays 实现的;LinkedList是基于双向列表实现的。
2、ArrayList 查询速度快,但是插入速度慢;LinkedList插入速度快,查询速度慢。
hashMap底层是怎么实现的?
hashMap resize机制
HashMap的扩容机制就是重新申请一个容量是当前的2倍的桶数组,然后将原先的记录逐个重新映射到新的桶里面,然后将原先的桶逐个置为null使得引用失效。后面会讲到,HashMap之所以线程不安全,就是resize这里出的问题。
HashMap为啥是非线程安全的?
是因为HashMap扩容导致的
由于HashMap的容量是有限的,如果HashMap中的数组的容量很小,假如只有2个,那么如果要放进10个keys的话,碰撞就会非常频繁,此时一个O(1)的查找算法,就变成了链表遍历,性能变成了O(n),这是Hash表的缺陷。
为了解决这个问题,HashMap设计了一个阈值,其值为容量的0.75,当HashMap所用容量超过了阈值后,就会自动扩充其容量。
在多线程的情况下,当重新调整HashMap大小的时候,就会存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。如果条件竞争发生了,那么就会产生死循环了。
HashMap遍历
使用迭代器一:效率高
Map<String, String> map = new HashMap<>();
map.put("a", "1");
map.put("b", "2");
Iterator sm = map.entrySet().iterator();
while (sm.hasNext()) {
Map.Entry s = (Map.Entry)sm.next();
System.out.println(s.getKey());
System.out.println(s.getValue());
}
使用迭代器二:效率低
Map<String, String> map = new HashMap<>();
map.put("a", "1");
map.put("b", "2");
Iterator sm = map.keySet().iterator();
while (sm.hasNext()) {
Object key = sm.next();
System.out.println(key);
System.out.println(map.get(key));
}
For循环 keySet entrySet
Map<String, String> map = new HashMap<>();
map.put("a", "1");
map.put("b", "2");
for(String key : map.keySet()){
System.out.println(map.get(key));
}
for(Map.Entry<String,String> entry : map.entrySet()){
System.out.println(entry.getValue());
}
Java8 Lambda表达式遍历
Map<String, String> map = new HashMap<>();
map.put("a", "1");
map.put("b", "2");
map.forEach((key,value)->{
System.out.println(key + ":" + value);
});
map diff 的话,主要有diff哪些方面呢
异常体系,exception 和error的区别
Error表示在运行期间出现了非常严重的错误,并且这个错误是不可恢复的,这种错误会导致程序终止运行
Exception 表示可恢复的异常,是编译器可以捕捉到的,它包含两种类型:检查异常和运行时异常。
检查异常:比如最常见的IO异常和SQL异常,这种异常发生在编译阶段
运行时异常,编译器没有对这个异常做强制处理例如空指针异常,数组越界异常
JVM面试题
https://ld246.com/article/1583743114636
JVM加载class文件的原理
java是动态解释性语言,.class只有被加载到jvm中才能运行。
类的加载由ClassLoader和它的子类实现,负责把类文件从硬盘加载到内存中。
类的加载分隐式加载和显示加载,隐式加载即指程序在使用new方式创建对象时,会隐式调用类的加载器把对应的类加载到JVM中。
显式加载指通过调用class.forName()把需要的类加载到JVM中。
程序启动,最初只讲保证程序运行的基础类加载到JVM中,其他类则在需要时才加载。
JVM如何构建实例
当遇到 new 指令的时候,检查这个类是否被加载,没被加载的话加载,然后为对象分配内存空间并进行默认初始化,执行方法。
JVM内存
栈、堆、方法区啥的都是JVM内存,只是人为划分
GC是什么
垃圾回收,主要作用是收回程序中不再使用对象的内存空间。
垃圾回收器主要负责 分配内存;确保被引用的对象不被错误的回收;回收不再被引用的对象的内存空间
使用有线图来记录和管理堆内存中的所有对象。
使用System.gc()可以提醒垃圾回收器运行,但是不能保证垃圾回收器会立即执行。
垃圾判断算法
1.引用计数法
给每个对象添加一个计数器,当有地方引用该对象的时候,计数器加1.当引用失效时计数器减1.用对象计数器是否为0来判断对象是否可被回收。
2.可达性分析算法(追踪回收算法)
利用JVM维护的对象引用图,从跟节点开始遍历对象的应用图,同时标记遍历到的对象。遍历结束后,未被标记的对象就是目前已不被使用的对象,可以被收回。
3、压缩回收算法
把堆中活动的对象移动到堆中一端,在这个过程中同时也对堆中的碎片进行了处理。这种方法虽然可以消除碎片,但每次处理都会带来性能损失。
4、复制回收算法
把堆分成两个大小相同的区域,在任何时刻只有其中一个区域被使用,直到这个区域被消耗完。此时垃圾回收器会中断程序的执行,遍历把所有活动的对象复制到另一个区域中,从而消除内存碎片。
优点:消除了内存碎片
缺点:对指定大小的堆需要两倍的内存空间;同时由于在内存调整的过程中要中断当前执行的程序,降低了程序执行效率。
5、按代回收算法
在复制回收算法的基础上,把生命周期较长的对象转移到高一级的堆里,减少对其的扫描次数。
JVM在什么时候进行垃圾回收?
1会在cpu空闲的时候自动进行回收
2在堆内存存储满了之后
3主动调用System.gc()后尝试进行回收
栈和堆的区别
1、栈中存 变量 + 基本数据类型 + 引用变量 地址;堆中存放对象。
2、栈 通过压栈和弹栈的操作 将栈中空间收回;堆中的对象通过垃圾回收机制收回。
3、栈的存取速度快,堆的慢。
内存泄露
一个不再被程序使用的对象或变量还在内存中占有存储空间。
一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存泄漏主要有两种情况:一是在堆中申请的空间没有被释放;二是对象已不再使用,但仍在内存中保留。Java的内存泄漏主要指第二种情况
内存泄露的场景
1、静态集合类:
如 HashMap、LinkedList 等等。如果这些容器为静态变量,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
2、各种连接:
如数据库连接、网络连接和 IO 连接等。在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用 close 方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对 Connection、Statement 或 ResultSet 不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
3、变量不合理的作用域:
一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为 null,很有可能导致内存泄漏的发生。
4、内部类持有外部类:
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
5、改变哈希值:
当一个对象被存储进 HashSet 集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中单独删除当前对象,造成内存泄露。
6、单例模式:
不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被 JVM 正常回收,导致内存泄漏。
如何避免内存泄漏
未对作废数据内存单元置为 null,尽早释放无用对象的引用,使用临时变量时,让引用变量在推出活动域后自动设置为 null,暗示垃圾收集器收集;程序避免用 String 拼接,用 StringBuffer,因为每个 String 会占用内存一块区域;尽量少用静态变量(全局不会回收);不要集中创建对象尤其大对象,可以使用流操作;尽量使用对象池,不再循环中创建对象,优化配置;创建对象到单例 getInstance 中,对象无法回收被单例引用;服务器 session 时间设置过长也会引起内存泄漏。
内存溢出(分配的内存不够用)
指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储 int 类型数据的存储空间,但是你却存储 long 类型的数据,那么结果就是内存不够用,此时就会报错 OOM,即所谓的内存溢出。
内存溢出可能原因和解决。
原因可能是:
A,数据加载过多,如 1 次从数据库中取出过多数据,->分页加载数据
B,集合类中有对对象的引用,用完后没有清空或者集合对象未置空导致引用存在等,使得 JVM 无法回收
C,死循环,过多重复对象
D,第三方软件的 bug
E,启动参数内存值设定的过小。
修改 JVM 启动参数,加内存(-Xms,-Xmx);错误日志,是否还有其他错误;代码走查
请写出几段可以导致内存溢出、内存泄漏、栈溢出的代码
内存溢出(数组中不断添加对象)
内存泄漏 ( 静态变量引用对象、长字符串 Intern、未关闭流)
栈溢出 ( 无限递归)
什么是跨域及跨域解决方法
首先一个url是由:协议、域名、端口 三部分组成。(一般端口默认80),如:https://blog.moonlet.cn:80。当一个请求url的协议、域名、端口三者之间的任意一个与当前页面url不同即为跨域
跨域产生的原因是:浏览器的同源策略限制
所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
非同源会出现的限制:无法读取非同源网页的cookie、localstorage等;无法接触非同源网页的DOM和js对象;无法向非同源地址发送Ajax请求;
跨域解决方法:
1、nginx反向代理
server{
location /api {
//拦截一下
proxy_pass http://www.baidu.com:81;
}
}
2、添加响应头
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式
3、通过jsonp跨域
在浏览器中,