Java笔记思维导图
2023-07-04 16:50:57 0 举报
AI智能生成
java相关笔记思维导图
作者其他创作
大纲/内容
基本数据类型
占用字节: 1 2 4 8
整型: byte short int long
字符型: char
浮点型: float double
布尔型: boolean
1字节(byte) = 8位(bit)
整型: byte short int long
字符型: char
浮点型: float double
布尔型: boolean
1字节(byte) = 8位(bit)
七大原则
单一职责原则
一个类只负责一项职责
接口隔离原则
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
依赖倒转原则
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转的中心思想是面向接口编程
- 依赖倒转原则的设计理念是:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多,在Java中,抽象指的是接口或者抽象类,细节就是具体的实现类
- 在使用接口或抽象类的目的是制定好规范,而不涉及具体的操作,把展现细节的任务交给他们的实现类取完成
总结:一句话就是,定义好接口规范才是重中之重!有了规范接口,剩下的实现就交给具体的实现类去做!
依赖传递的三种方式
接口传递
构造器传递
setter传递
注意事项和细节
低模块尽量都要有抽象类或接口,或两者都有,程序的稳定性更好
变量的声明类型尽量是接口或抽象类,这样我们的变量引用和实际对象之间就存在一个缓冲层,利于程序的扩展和优化
继承时遵循里氏替换原则
里氏替换原则
通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能
开闭原则
对扩展:开放
对修改:关闭
对修改:关闭
迪米特法原则
又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。
个人理解为:通过传参的方式进行聚合(形参里是“直接朋友关系”)
个人理解为:通过传参的方式进行聚合(形参里是“直接朋友关系”)
合成复用原则
尽量使用合成或聚合的方式,而不是使用继承
JVM
JMM:Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
线程中分为 工作内存、主内存
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁
关于JMM的一些同步的约定:
线程中分为 工作内存、主内存
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁
volatile
1、保证可见性
2、不保证原子性
3、禁止指令重排
抽象思想,自己思考
要基于接口,抽象的去设计代码,以及使用的时候,要时刻记住可用参数进行传递!
JUC
JUC:指的是java.util三个并发编程工具包
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
实现多线程的四种方式:
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
4.线程池
Runnable接口和Callable接口区别:
- 是否有返回值:Runnable无返回值,Callable有返回值
- 是否抛出异常:call方法计算一个结果,如果不能这样做,就会抛出异常
- 实现方法名称不同,Runnable接口是run方法,Callable接口是call方法
进程和线程
- 进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单元。
- 线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单元。
- 进程:是一个程序,一个进程包含多个线程,且至少包含一个线程。
- Java默认有两个线程:main 和 GC。
Java是不能开启线程的,底层是调用start0()是一个native方法,由底层的C++方法编写。java无法直接操作硬件。
线程的六种状态
1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3. 阻塞(BLOCKED):表示线程阻塞于锁。
4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。
java多线程中的阻塞状态跟等待状态到底怎么才能理解他们的不同?
1、一句话概括
在java中,线程阻塞状态是线程本身不可计划的,而线程等待状态是线程本身计划之内的。
2、相同点与不同点
相同点:
(1)都会暂停线程的执行。
区别点:
(1)线程进入阻塞状态是被动的, 而线程进入等待状态是主动的。
阻塞状态的被动:线程在同步代码外(就是snychronized或者lock—unlock代码块之外),获取对象锁失败时,线程进入阻塞状态;何时获取对象锁失败不可知,即线程阻塞状态是线程本身不可计划的。
等待状态的主动:线程在同步代码内,等待其他线程操作时,线程接入等待状态;何时等待其他线程操作可知,即线程等待状态是线程本身计划之内的。
Tip:再简单的说就是,同步代码块以外的是阻塞状态,以内的是等待状态!
在java中,线程阻塞状态是线程本身不可计划的,而线程等待状态是线程本身计划之内的。
2、相同点与不同点
相同点:
(1)都会暂停线程的执行。
区别点:
(1)线程进入阻塞状态是被动的, 而线程进入等待状态是主动的。
阻塞状态的被动:线程在同步代码外(就是snychronized或者lock—unlock代码块之外),获取对象锁失败时,线程进入阻塞状态;何时获取对象锁失败不可知,即线程阻塞状态是线程本身不可计划的。
等待状态的主动:线程在同步代码内,等待其他线程操作时,线程接入等待状态;何时等待其他线程操作可知,即线程等待状态是线程本身计划之内的。
Tip:再简单的说就是,同步代码块以外的是阻塞状态,以内的是等待状态!
图解线程状态
https://pic4.zhimg.com/80/v2-1319f27379e4745d02b40ea12b9307cb_720w.webp
wait/sleep区别
1.来自不同的类
wait => Object,任何对象实例都能调用
sleep => Thread,Thread的静态方法
wait => Object,任何对象实例都能调用
sleep => Thread,Thread的静态方法
2.关于锁的释放
wait会释放锁;sleep不会释放锁,它也不需要占用锁
wait会释放锁;sleep不会释放锁,它也不需要占用锁
3.使用范围、捕获异常不同
wait:必须在同步代码块中使用,不需要捕获异常
sleep:可以在任何地方使用,必须要捕获异常
wait:必须在同步代码块中使用,不需要捕获异常
sleep:可以在任何地方使用,必须要捕获异常
并发、并行
并发: 同一时刻多个线程访问同一个资源(多线程共享资源)
例如:春运抢票、电商秒杀
例如:春运抢票、电商秒杀
并行: 多项工作一起执行,之后再汇总(可理解为异步)
例如:泡方便面,电水壶烧水的同时,拆开泡面调料倒入桶中
例如:泡方便面,电水壶烧水的同时,拆开泡面调料倒入桶中
用户线程和守护线程
- 用户线程:自定义线程(new Thread())
- 守护线程:后台中一种特殊的线程,比如垃圾回收
锁
公平锁、非公平锁、自旋锁、可重入锁、死锁
公平锁:非常公平,不能插队,必须先来后到!(效率可能较低)
非公平锁:非常不公平,可以插队(默认都是非公平,效率较高)
可重入锁(递归锁):拿到外边的锁后,会自动拿到里面的锁(synchronized【隐式】和Lock【显式】都是可重入锁)
隐式:不需要手动去操作加锁,释放锁
显示:需要手动去操作加锁、释放锁
隐式:不需要手动去操作加锁,释放锁
显示:需要手动去操作加锁、释放锁
自旋锁:等待获取锁,如未获取到锁进行等待阻塞状态,直至尝试获取到锁!
死锁:两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再继续执行下去。从而导致死锁的发生
如何定位死锁,解决问题?
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack 进程进程号 找到死锁信息(jstack是jvm中自带的堆栈跟踪工具)
命令:jstack 进程号
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack 进程进程号 找到死锁信息(jstack是jvm中自带的堆栈跟踪工具)
命令:jstack 进程号
产生死锁的四个必要条件:
- 互斥条件(Mutual Exclusion):每个资源最多只能由一个线程持有。
- 请求与保持条件(Hold and Wait):线程持有至少一个资源,并且在等待其他线程拥有的资源。
- 不可抢占条件(No Preemption):资源只能在持有者释放之后由其他线程获取,不能被强制抢占。
- 循环等待条件(Circular Wait):存在一个线程等待列表的循环链,每个线程等待链中的资源被下一个线程持有。
Lock锁
ReentranLock(可重入锁)
默认非公平锁,可通过构造方法传入true参数,启用公平锁
ReentrantReadWriteLock.ReadLoack(读锁)
ReentrantReadWriteLock.WriteLoack(写锁)
ReentrantReadWriteLock.WriteLoack(写锁)
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
*
* ReadWriteLock
* 读-读 可以共存!
* 读-写 不可共存!
* 写-写 不可共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int temp = i;
//写入
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int temp = i;
//读取
new Thread(() -> {
System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + ""));
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 存 写
*/
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "写入" + key);
try {
TimeUnit.MILLISECONDS.sleep(200);
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
System.out.println(Thread.currentThread().getName() + "写入OK");
}
/**
* 取 读
*/
public Object get(String key) {
readWriteLock.readLock().lock();
Object o = null;
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
*
* ReadWriteLock
* 读-读 可以共存!
* 读-写 不可共存!
* 写-写 不可共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int temp = i;
//写入
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int temp = i;
//读取
new Thread(() -> {
System.out.println("获取" + temp + "缓存数据-> " + myCache.get(temp + ""));
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 存 写
*/
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "写入" + key);
try {
TimeUnit.MILLISECONDS.sleep(200);
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
System.out.println(Thread.currentThread().getName() + "写入OK");
}
/**
* 取 读
*/
public Object get(String key) {
readWriteLock.readLock().lock();
Object o = null;
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
底层是通过javaAPI实现
synchronized
默认非公平锁
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为一下3种形式。
具体表现为一下3种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是synchronized括号里配置的对象。
底层是,通过进入和退出 Monitor 对象实现锁机制,代码块通过一对 monitorenter/monitorexit 指令实现(这两个指令必须成对存在)。依赖于JVM虚拟机
Lock跟synchronized区别:
1.synchronized 是内置的Java关键字,Lock是Java的一个接口
2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized 会自动释放锁,Lock必须手动释放锁!如果不释放锁,会造成死锁
4.synchronized 线程一,在获得锁的情况下,线程二获取不到锁,阻塞了,线程二就只能傻傻的等着;Lock就不一定会等待下去
5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平/公平(可以自己设置,默认非公平锁)
6.synchronized 适合锁少量的同步代码;Lock适合锁大量同步代码!
7.Lock可以提高多个线程进行读操作的效率。
注:在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时(即大量线程同时竞争),此时Lock的性能要远远优于synchronized
如果不加lock和synchronized ,怎么样保证原子性?使用原子类:CAS原理
2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized 会自动释放锁,Lock必须手动释放锁!如果不释放锁,会造成死锁
4.synchronized 线程一,在获得锁的情况下,线程二获取不到锁,阻塞了,线程二就只能傻傻的等着;Lock就不一定会等待下去
5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平/公平(可以自己设置,默认非公平锁)
6.synchronized 适合锁少量的同步代码;Lock适合锁大量同步代码!
7.Lock可以提高多个线程进行读操作的效率。
注:在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时(即大量线程同时竞争),此时Lock的性能要远远优于synchronized
如果不加lock和synchronized ,怎么样保证原子性?使用原子类:CAS原理
CAS原理
AtomicInteger count = new AtomicInteger(10); // 初始化 count 变量为 10
int expect = 10; // 预期值为 10
int update = 20; // 新的值为 20
count.decrementAndGet();
boolean result = count.compareAndSet(expect, update); //结果修改成功,新值为20
//如果上面的expect = 11(不是跟初始值10一样)那么cas就失败,修改失败!
int expect = 10; // 预期值为 10
int update = 20; // 新的值为 20
count.decrementAndGet();
boolean result = count.compareAndSet(expect, update); //结果修改成功,新值为20
//如果上面的expect = 11(不是跟初始值10一样)那么cas就失败,修改失败!
常用辅助类:
CountDownLatch(减法计数器)
意思就是每个线程执行countDown() 数量-1,都会进行await(),当计数器变为0,就会被唤醒,继续往下执行!
CyclicBarrier(加法计数器)
原理与减法计数器一样,但是这个可以重复使用,比如从0~30为一周前,30次后,会重置为0,继续使用!
Semaphore(信号量)
原理:
semaphore.acquire(); //获得,如果已经满了,等待,等待被释放为止!
semaphore.release(); // 释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
比如停车位:有6辆车,但是只有3个停车位!那么acquire()就是获取停车位,3个停车位还没满,就能直接停,如果3个满了就等待,直到其他停车位被释放(就是别的车离开了这个停车位)后才能获得停车位!
semaphore.acquire(); //获得,如果已经满了,等待,等待被释放为止!
semaphore.release(); // 释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
比如停车位:有6辆车,但是只有3个停车位!那么acquire()就是获取停车位,3个停车位还没满,就能直接停,如果3个满了就等待,直到其他停车位被释放(就是别的车离开了这个停车位)后才能获得停车位!
//线程数量:停车位! 限流!注:此时停车位就3个,而下面的for循环6次代表有6辆车
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
//acquire() 获得
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); //release() 释放
}
},String.valueOf(i)).start();
}
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
//acquire() 获得
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); //release() 释放
}
},String.valueOf(i)).start();
}
Unsafe类
Java无法操作内存,那么就通过这个类去访问底层资源、去操作内存
CAS也是乐观锁的一种实现方式!
虚假唤醒问题,将if改为while
public void increment() {
lock.lock();
try {
/*if (num != 0) {
wait();
}*/
//防止虚假唤醒:因为用if的话,那么当wait后线程被虚假的唤醒了,就立即执行了wait();下面的代码而不经过判断。那么会造成以下问题:
//1、程序逻辑混乱:如果线程在未满足其等待条件时被错误唤醒,则可能会导致程序逻辑出现问题。
//2、性能下降:如果线程频繁虚假唤醒,则可能会导致性能下降,因为线程需要不断地从等待状态转换到运行状态。
//要避免虚假唤醒,就需要讲if改为while判断
while (num != 0) {
//注意:不可使用wait()或者condition.wait();因为wait()方法是Object对象提供的,而不是对象condition去等待!!!
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我+1 完毕了
//lock.notifyAll();在使用Lock锁时,不能使用wait()和notifyAll()方法,因为它们只能在synchronized块中使用。需要使用Condition接口的await()和signalAll()方法来代替这两个方法。
//注意:不可使用condition.notifyAll();因为notifyAll方法是Object对象提供的,而不是对象condition去唤醒!!!
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
lock.lock();
try {
/*if (num != 0) {
wait();
}*/
//防止虚假唤醒:因为用if的话,那么当wait后线程被虚假的唤醒了,就立即执行了wait();下面的代码而不经过判断。那么会造成以下问题:
//1、程序逻辑混乱:如果线程在未满足其等待条件时被错误唤醒,则可能会导致程序逻辑出现问题。
//2、性能下降:如果线程频繁虚假唤醒,则可能会导致性能下降,因为线程需要不断地从等待状态转换到运行状态。
//要避免虚假唤醒,就需要讲if改为while判断
while (num != 0) {
//注意:不可使用wait()或者condition.wait();因为wait()方法是Object对象提供的,而不是对象condition去等待!!!
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程我+1 完毕了
//lock.notifyAll();在使用Lock锁时,不能使用wait()和notifyAll()方法,因为它们只能在synchronized块中使用。需要使用Condition接口的await()和signalAll()方法来代替这两个方法。
//注意:不可使用condition.notifyAll();因为notifyAll方法是Object对象提供的,而不是对象condition去唤醒!!!
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
阻塞队列
什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
四组API
方式 抛出异常 不抛出异常 阻塞等待 超时等待
添加 add() offer() put() offer(E e, long timeout, TimeUnit unit)
移除 remove() poll() take() poll(long timeout, TimeUnit unit)
获取队首元素 element() peek() - -
添加 add() offer() put() offer(E e, long timeout, TimeUnit unit)
移除 remove() poll() take() poll(long timeout, TimeUnit unit)
获取队首元素 element() peek() - -
同步队列
SynchronousQueue
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put()、take()
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put()、take()
线程池(重点)
三大方式、七大参数、四种拒绝策略
三大方法:(不是重点)
Executors.newSingleThreadExecutor();//创建单个线程的线程池
Executors.newFixedThreadPool(5);//创建固定线程的线程池
Executors.newCachedThreadPool();//创建可伸缩线程池
Executors.newSingleThreadExecutor();//创建单个线程的线程池
Executors.newFixedThreadPool(5);//创建固定线程的线程池
Executors.newCachedThreadPool();//创建可伸缩线程池
七大参数:
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂:创建线程,一般不用动
RejectedExecutionHandler handler) { //拒绝策略
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂:创建线程,一般不用动
RejectedExecutionHandler handler) { //拒绝策略
4大拒绝策略:
* new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!
* new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常!
线程池的好处
1、降低资源的消耗;
2、提高响应的速度;
3、方便线程管理。
1、降低资源的消耗;
2、提高响应的速度;
3、方便线程管理。
不允许使用Executors创建,而是使用ThreadPoolExecutor去创建,原因是,Executors去默认创建的参数是Integer.MAX_VALUE≈21亿,会导致内存溢出问题!
线程池最大线程数如何设置?
IO密集型,CPU密集型(调优)
IO密集型,CPU密集型(调优)
1、CPU 密集型 电脑处理器数是几,就是几,可以保证CPU的效率最高!
2、IO 密集型 大于 程序中十分耗IO的线程数 ---> 程序中 15个大型任务 io十分占用资源! =》 30
集合
Collection
List
ArrayList(数组)
线程不安全,但效率高,想要线程安全用Vecotr
Vector(数组)
线程安全,但效率较低
LinkedList(双向链表)
线程不安全,解决方法:
Collections.synchronizedList(new LinkedList<>());
Collections.synchronizedList(new LinkedList<>());
Set
HashSet
底层亦是HashMap
线程不安全,解决如下:
//解决方案一:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
//解决方案二:
Set<String> set = new CopyOnWriteArraySet<>();
线程不安全,解决如下:
//解决方案一:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
//解决方案二:
Set<String> set = new CopyOnWriteArraySet<>();
Map
HashMap
线程不安全,解决如下:
1.Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
2.Map<String, Object> map = new ConcurrentHashMap<>();
1.Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
2.Map<String, Object> map = new ConcurrentHashMap<>();
Queue(队列)
存储的元素是有序的、可重复的。
扩容相关知识点
- ArrayList
- HashMap
- Hashtable
ArrayList
默认初始容量大小:10
默认扩容因子:无,发现不够就去扩容
扩容:原始大小×1.5
默认扩容因子:无,发现不够就去扩容
扩容:原始大小×1.5
HashMap
默认初始容量大小:16
默认扩容因子:0.75
扩容:原始大小×2
默认扩容因子:0.75
扩容:原始大小×2
HashMap 的底层实现
JDK1.8 之前:
- 数组+链表
JDK1.8之后:
- 数组+链表+红黑树
红黑树
- 二叉查找树是二分查找的思想,查找所需的最大次数等同于二叉树的高度。
- 在插入节点的时候也是利用类似的方法,一层一层比较大小,找到合适的插入位置。
- 解决二叉查找树多次插入新节点而导致的不平衡的方法,就是使用红黑树。
- 红黑树是一种自平衡的二叉查找树。除了符合二叉查找树的基本特性外,还具有下列的附加特性:
- 根节点是黑色。
- 节点是红色或黑色。
- 每个叶子节点都是黑色的空节点(NIL节点)。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
- 所有插入的节点,默认为红色
左旋
当前节点的父结点是红色,叔叔(爷爷节点的下面另一个子节点)是黑色的时候,且当前的结点是右子树。左旋,以父节点作为左旋
右旋
当前节点的父结点是红色,叔叔(爷爷节点的下面另一个子节点)是黑色的时候,且当前的结点是左子树。右旋,以父节点作为右旋
Hashtable
默认初始容量大小:11
默认扩容因子:0.75
扩容:原始大小×2 + 1
默认扩容因子:0.75
扩容:原始大小×2 + 1
Spring
Bean
Bean是由Spring的Bean工厂创建的,那是怎么创建的呢,在程序运行的时候,Spring会通过XML的解析类、注解解析类去进行扫描Bean,将扫描得到的Bean信息,映射为BeanDefinition类,再将每个BeanDefinition加入到一个Map集合里,然后再有工厂去这个map里面拿取到bean的信息,之后创建出来,交由SpringIOC容器进行保管!且默认都是单例
IOC控制反转
AOP面向切面编程
DI依赖注入
Spring的动态代理选择原则
(1)当Bean有实现接口时,Spring就会用JDK动态代理。
(2)当Bean没有实现接口时,Spring会选择CGLib代理。
(2)当Bean没有实现接口时,Spring会选择CGLib代理。
二十三种设计模式
创建型模式
单例模式
双检锁单例
//双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if (null == lazyMan) {
synchronized (LazyMan.class) {
if (null == lazyMan) {
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
单例不安全, 因为反射,可以使用枚举类做单例!
public static LazyMan getInstance() {
if (null == lazyMan) {
synchronized (LazyMan.class) {
if (null == lazyMan) {
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
单例不安全, 因为反射,可以使用枚举类做单例!
静态内部类单例(推荐)
(亦解决了反射会破坏单例)
(亦解决了反射会破坏单例)
class LazySingleton {
String name = "单例";
private static boolean initialized = false;
private LazySingleton() {
//防止反射对其单例模式进行破坏
if (initialized) {
throw new RuntimeException("不允许创建多个实例!");
}
initialized = true;
}
static LazySingleton getInstance() {
return LazyHolder.LAZY_SINGLETON;
}
private final static class LazyHolder {
private static final LazySingleton LAZY_SINGLETON = new LazySingleton();
}
/**
* 防止序列化破坏单例模式
*/
@Serial
private Object readResolve() {
return LazyHolder.LAZY_SINGLETON;
}
}
String name = "单例";
private static boolean initialized = false;
private LazySingleton() {
//防止反射对其单例模式进行破坏
if (initialized) {
throw new RuntimeException("不允许创建多个实例!");
}
initialized = true;
}
static LazySingleton getInstance() {
return LazyHolder.LAZY_SINGLETON;
}
private final static class LazyHolder {
private static final LazySingleton LAZY_SINGLETON = new LazySingleton();
}
/**
* 防止序列化破坏单例模式
*/
@Serial
private Object readResolve() {
return LazyHolder.LAZY_SINGLETON;
}
}
枚举单例模式(强烈推荐)
/**
* 枚举单例模式
*/
@Getter
enum EnumSingleton implements Serializable {
SINGLETON();
private Integer code;
private String name;
public static EnumSingleton getSingletonInstance() {
return SINGLETON;
}
}
使用枚举实现的单例模式是最安全的,无法通过反射进行实例化。当我们使用枚举来实现单例时,枚举本身就提供了保证单例唯一性和安全性的机制。
* 枚举单例模式
*/
@Getter
enum EnumSingleton implements Serializable {
SINGLETON();
private Integer code;
private String name;
public static EnumSingleton getSingletonInstance() {
return SINGLETON;
}
}
使用枚举实现的单例模式是最安全的,无法通过反射进行实例化。当我们使用枚举来实现单例时,枚举本身就提供了保证单例唯一性和安全性的机制。
原型模式
浅拷贝
/**
* 浅拷贝
* 拷贝(克隆)PrototypePattern对象,引用地址是不同,即是两个不同的实例,但是里面的属性age、name是引用相同的地址,这就是浅拷贝!
*/
@Data
class PrototypePattern implements Cloneable{
private int age = 20;
private String name = "浅拷贝";
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* 或者下面的浅克隆方法
*/
// protected PrototypePattern clone() {
// PrototypePattern prototypePattern = new PrototypePattern();
// prototypePattern.setAge(this.getAge());
// prototypePattern.setName(this.getName());
// return prototypePattern;
// }
}
* 浅拷贝
* 拷贝(克隆)PrototypePattern对象,引用地址是不同,即是两个不同的实例,但是里面的属性age、name是引用相同的地址,这就是浅拷贝!
*/
@Data
class PrototypePattern implements Cloneable{
private int age = 20;
private String name = "浅拷贝";
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* 或者下面的浅克隆方法
*/
// protected PrototypePattern clone() {
// PrototypePattern prototypePattern = new PrototypePattern();
// prototypePattern.setAge(this.getAge());
// prototypePattern.setName(this.getName());
// return prototypePattern;
// }
}
深拷贝
/**
* 深拷贝:意义在于拷贝的对象,里外都是要不一样(即地址要不一样)!
* 拷贝(克隆)PrototypePattern对象,引用地址是不同,即两个不同的实例。且里面的属性age、name、组合的对象,也都是引用不同的地址,这就是深拷贝!
*/
@Data
class PrototypePattern implements Cloneable, Serializable {
private int age = 20;
private String name = "深拷贝";
/**
* 类里组合了其他的类,或者数组List,必须要初始化,没有初始化就是null,那么即使是深拷贝,也是指向null的这个引用对象,那么深拷贝也是一样的
*/
private List<String> list = new ArrayList<>();
@Override
protected Object clone() throws CloneNotSupportedException {
//序列化方式深拷贝一:将序列化的对象数据写入内存中的字节数组中。
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
//序列化方式深拷贝二:将序列化的对象数据写入到文件中。这里不再代码赘述
}
}
* 深拷贝:意义在于拷贝的对象,里外都是要不一样(即地址要不一样)!
* 拷贝(克隆)PrototypePattern对象,引用地址是不同,即两个不同的实例。且里面的属性age、name、组合的对象,也都是引用不同的地址,这就是深拷贝!
*/
@Data
class PrototypePattern implements Cloneable, Serializable {
private int age = 20;
private String name = "深拷贝";
/**
* 类里组合了其他的类,或者数组List,必须要初始化,没有初始化就是null,那么即使是深拷贝,也是指向null的这个引用对象,那么深拷贝也是一样的
*/
private List<String> list = new ArrayList<>();
@Override
protected Object clone() throws CloneNotSupportedException {
//序列化方式深拷贝一:将序列化的对象数据写入内存中的字节数组中。
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
//序列化方式深拷贝二:将序列化的对象数据写入到文件中。这里不再代码赘述
}
}
工厂方法模式
工厂方法模式关注的是一个具体产品的创建,它定义了一个创建对象的接口(工厂接口),但将具体对象的创建延迟到子类中实现。工厂方法模式适用于需要在运行时根据某些条件决定创建哪个具体对象的情况。它的特点是具体的工厂类负责创建具体的产品类,每个具体产品类对应一个具体的工厂类。
抽象工厂模式
抽象工厂模式关注的是一组相关产品的创建,它提供了一个创建相关产品的接口(抽象工厂接口),具体的产品创建交由具体的工厂类实现。抽象工厂模式适用于需要一次性创建一系列相关的产品,而不是单独创建一个对象的情况。它的特点是每个具体工厂类负责创建一组相关的产品类,每个产品类对应一个具体工厂类。
建造者模式
结构型模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
池化技术,如数据库连接池,线程池!
代理模式
是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,代
理模式属于结构型设计模式。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
Tip:说白了就是对一个对象进行代理,将获取到这个对象的所有功能,然后我们可以进行对这个对象功能的增强以及保护。
增强意为原本对象的功能方法call(),那么我们可以在调用call()方法的前后进行功能增加,比如:
System.out.println("增强功能前");
call();
System.out.println("增强功能后");
理模式属于结构型设计模式。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
Tip:说白了就是对一个对象进行代理,将获取到这个对象的所有功能,然后我们可以进行对这个对象功能的增强以及保护。
增强意为原本对象的功能方法call(),那么我们可以在调用call()方法的前后进行功能增加,比如:
System.out.println("增强功能前");
call();
System.out.println("增强功能后");
静态代理
静态代理只能通过手动完成代理操作,如果被代理类增加了
新的方法,代理类需要同步增加,违背开闭原则。所有基本是不会去使用静态代理。
新的方法,代理类需要同步增加,违背开闭原则。所有基本是不会去使用静态代理。
动态代理
JDK动态代理
JDK使用反射机制生成代理接口,调用InvokeHandler处理;
JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
Java提供静态方法:Proxy.newInstance();方法进行动态代理
JDK动态代理生成对象的步骤如下:
(1)获取被代理对象的引用,并且获取它的所有接口,反射获
取。
(2)JDK动态代理类重新生成一个新的类,同时新的类要实现被
代理类实现的所有接口。
(3)动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码
调用(在代码中体现)。
(4)编译新生成的Java代码.class文件。
(5)重新加载到JVM中运行。
JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
Java提供静态方法:Proxy.newInstance();方法进行动态代理
JDK动态代理生成对象的步骤如下:
(1)获取被代理对象的引用,并且获取它的所有接口,反射获
取。
(2)JDK动态代理类重新生成一个新的类,同时新的类要实现被
代理类实现的所有接口。
(3)动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码
调用(在代码中体现)。
(4)编译新生成的Java代码.class文件。
(5)重新加载到JVM中运行。
CGLib动态代理
CGLib则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的
因为是继承机制,不能代理final修饰的类。
因为是继承机制,不能代理final修饰的类。
CGLib和JDK动态代理对比
(1)JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象。
(2)JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态
代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,
CGlib代理实现更复杂,生成代理类比JDK动态代理效率低。
(3)JDK动态代理调用代理方法是通过反射机制调用的。
CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。
(2)JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态
代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,
CGlib代理实现更复杂,生成代理类比JDK动态代理效率低。
(3)JDK动态代理调用代理方法是通过反射机制调用的。
CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。
行为型模式
责任链模式
命令模式
解释器模式
迭代器模式
备忘录模式
策略模式
模板方法模式
观察者模式
状态模式
访问者模式
中介者模式
工作中遇到问题
1、Dubbo远程服务调用问题,采用的序列化方式为fastjson2,A模块服务,调用B模块服务时,B抛出自定义异常且带的参数是自定义枚举类提示信息。然而dubbo一直报错该枚举类不支持输入,错误提示:“autoType not support input”,就是反序列化失败的原因。但是即便给该类加了实现序列化接口,也不行,因为是fastjson2,不是fastjson,所以查了好多信息,也添加了dubbo配置等等,都失败。那就换个异常抛,之前没用过dubbo的异常处理,后面查了知道dubbo包下有RpcException异常类,就决定使用这个来进行抛异常。然后A服务调用方在设置全局异常,拦截RpcException异常的抛出处理后,再将信息返回给前端。其实这个问题也不是什么大问题,就fastjson的反序列化问题,。。。。。。。等大哥弄好包后再写
工作中!如何排查问题?
1、日志 90%
2、堆栈信息 10%
2、堆栈信息 10%
收藏
0 条评论
下一页