手撕单例模式
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。
在Java,一般常用在工具类的实现或创建对象需要消耗资源。
特点:类构造器私有、持有自己类型的属性、对外提供获取实例的静态方法
- 懒汉模式
线程不安全,延迟初始化,严格意义上不是不是单例模式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
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;
}
}
双重锁模式
线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()
对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile
修饰signleton
实例变量有效,解决该问题。
静态内部类单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
在以上所有的单例模式中,推荐静态内部类单例模式。非常直观,即保证线程安全又保证唯一性。
众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化。当实例被写入到文件到反序列化成实例时,我们需要重写readResolve
方法,以让实例唯一。
private Object readResolve() throws ObjectStreamException{
return singleton;
}
Java线程池各种参数
ThreadPoolExecutor的重要参数
1.corePoolSize:核心线程数:核心线程会一直存活,及时没有任务需要执行。
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
2.queueCapacity:任务队列容量(阻塞队列)
当核心线程数达到最大时,新任务会放在队列中排队等待执行。
3.maxPoolSize:最大线程数
当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。
4.keepAliveTime:线程空闲时间
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize。
如果allowCoreThreadTimeout=true,则会直到线程数量=0。
5.allowCoreThreadTimeout:允许核心线程超时
6.rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:(1)当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务。(2)当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常。
手撕two linkedlist megre to one
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head=new ListNode(0);
ListNode temp=head;
while(l1!=null && l2!=null){
if(l1.val>l2.val){
temp.next=l2;
l2=l2.next;
}else{
temp.next=l1;
l1=l1.next;
}
temp=temp.next;
}
if(l1!=null){
temp.next=l1;
}else{
temp.next=l2;
}
return head.next;
}
}
Linux命令
我这里简单列举,还要多多练习复杂的操作。
Linux的常用命令介绍:
问题:
因为平时开发的时候,对于服务器主机的操作系统是不需要界面的。
那么我们如何使用操作系统完成资源的操作呢?
使用:
命令方式
作用:
使用命令来替换界面的操作。
内容:
1、查看IP信息:ifconfig
2、进入指定的文件目录: cd
绝对路径: cd /目录/子目录/../..
相对路径: cd 当前路径的子目录/子目录/../..
注意 :
第一个 /表示根目录
3、退回上级目录:
cd .. 回退当上级目录,退一层
cd ../.. 回退两次
4、查看当前路径:pwd
5、查看当前目录下的内容:
ls:只显示文件名或者目录名
ll:以详细信息的方法列出当前目录的内容
6、自动补全:tab
注意:我们需要写出要操作的文件或者目录的从头开始的一部分唯一的名字。
7、清屏:clear
8、创建目录:
mkdir 文件名 在当前目录下创建指定的文件夹
mkdir /目录名/目录名/../../新的文件名 在指定的目录下创建新的文件夹
9、创建文件:
vi 新的文件名 示例:vi my.txt
注意:
会直接进入文本状态,需要点击键盘的i键或者insert键进入编辑状态。
书写内容后先点击esc键退出编辑状态。然后输入
:q! 强制退出
:q 退出,但不保存
: wq 保存并退出
vim 新的文件名 示例: vim you.java
vi和vim的区别:
vi命令打开的文本编辑器中没有颜色标识
vim命令的文本编辑器中带有关键字颜色
10、编辑现有文件:vi或者vim
vi 现有文件名
vim 现有文件名
11、查看文件内容:cat命令
cat 文件名 在控制显示所有的文件信息
12、查看指定前多少行数据: head命令
默认显示前10行
head -n 行数 文件名
13、查看指定后多少行数据:tail命令
默认显示后10行
tail -n 行数 文件名
14、动态查看文件的内容:tailf 命令
作用:动态的显示文件的内容,一旦文件内容增加了,控制台会立即显示出来
tailf -行数 文件名
默认显示后10行
15、追加内容:
echo 内容>>文件名
16、复制
cp 文件绝对路径 新的文件绝对路径
作用:将指定的文件复制到指定路径下的文件中
示例:cp /usr/local/wollo/my.txt /usr/mm.txt
注意:复制的同时重命名
cp 文件 新的文件名
作用:在当前目录下复制文件,同时重命名
cp -r 目录路径 新的路径
作用:将指定的目录下的全部复制到指定的路径下
示例: cp -r /usr/local/wollo /usr/wollo2
注意:同时可以对文件夹的名字进行重命名
17、删除
删除文件
rm 文件名
作用:删除当前目录下的指定文件
注意:会提示是否要删除,输入y删除,输入n取消
rm -rf 文件名:
作用:删除指定的文件
注意:不会提示,直接删除
删除目录
rm -r 目录名
作用:删除指定的目录
注意:会有提示语,但是不能在当前目录下删除自己。
rm -rf 目录名:强制删除
18、剪切
mv 文件 新的路径
19、解压文件
tar -zxvf 压缩包名
HashMap
这里也是简单写一下算了,具体多学。
//map
Map<Integer, Integer> map=new HashMap<Integer, Integer>();
int size()//K-V关系数量
boolean isEmpty()//是否为空
增:
V put(K key,V value)//放入K-V键值对
void putAll(Map<K,V> m)//放入m包含的所以键值对
删:
V remove(Object key)//删除key对应的键值对
void clear()//删除所有键值对
改:
直接put,会覆盖旧的记录
查:
boolean containsKey(Object key)//是否包含key
boolean containsValue(Object value)//是否包含value
V get(Object key)//得到key对应的value
生成集合:
Set<K> keySet()//返回包含所有key的set
Collection<V> values()//返回包含所有value的Collection
TreeMap特有:
public K firstKey()//返回第一个key(最高)
public K lastKey()//返回最后一个key(最低)
- 哈希冲突:若干Key的哈希值如果落在同一个数组下标上,将组成一条链,对Key的查找需要遍历链上的每个元素执行equals()比较,1.8后优化为红黑树
- 负载极限,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。默认当HashMap中的键值对达到数组大小的75%时,即会rehashing。
解释0.75:
是时间和空间成本上的一种折中:
- 较高的“负载极限”(也就是数组小)可以降低占用的空间,但会增加查询数据的时间开销
- 较低的“负载极限”(也就是数组大)会提高查询数据的性能,但会增加hash表所占用的内存开销
可以根据实际情况来调整“负载极限”值。
HashMap线程不安全
HashTable线程安全,实现的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
ConcurrentHashMap
线程安全,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。
ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。
只要多个修改操作发生在不同的段上,它们就可以并发进行。
默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶,读操作大部分时候都不需要用到锁。
(JDK1.8已经摒弃了Segment,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。)
jvm垃圾回收机制
标记-清除
该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
- 效率问题
- 空间问题(标记清除后会产生大量不连续的碎片)
复制
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
标记-整理
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
分代收集
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
HotSpot的算法
可达性分析会导致gc停顿,因为必须保证确保一致性,也就是冻结在某个时间点,所以我们必须要停顿。
但是停顿太久的代价我们是不能承受的。
在hotspot虚拟机中,采用OopMap结构记录哪些地方存着引用的。
但是,也不可能为每一个指令设置OopMap,只有在“安全点”才记录,gc也是在安全点才暂停。
安全点的选定不能太少:导致gc等待时间长
太多:导致过分增大负荷。
但是还不能解决问题:我们确实保证了运行时,隔一段时间就能进入安全点,但是不运行的时候呢?(程序未抢到cpu很久)
安全区:指这一段代码中,引用关系绝对不会发生变化。在这个区域中任意地方开始gc都是安全的。
concurrenthashmap原理
见上文
如何给项目数据库加锁?
前后端交互?
看你用的什么模板,我用的leaf
![](https://i-blog.csdnimg.cn/blog_migrate/9b63517e9462629ef0efa2c9d503c0b2.png)
项目的redis怎么用?
这个看我redis总结,或者你自己想想怎么用的。
项目Spring MVC如何接收参数?
MySQL事务隔离级别
事务的隔离级别分为:未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)、串行化(serializable)。
未提交读:A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚;---出现脏数据
已提交读:A事务执行更新;B事务查询;A事务又执行更新;B事务再次查询时,前后两次数据不一致;---不可重复读
可重复读:A事务无论执行多少次,只要不提交,B事务查询值都不变;B事务仅查询B事务开始时那一瞬间的数据快照;
MySQL的持久化和redis的持久化
还是看我redis总结
如何配置项目服务器?
linux-在cenos上安装大全(nginx/JRE/maven/Tomcat/MYSQL/redis/kafka/es...)
Linux如何查看端口冲突?
1.查找被占用的端口
netstat -tln
netstat -tln | grep 80
netstat -tln 查看端口使用情况,而netstat -tln | grep 80 则是只查看端口80的使用情况
2.查看端口属于哪个程序?端口被哪个进程占用
lsof -i :80
3.杀掉占用端口的进程
kill -9 进程id
如何查看项目日志?
1.tail
假定日志文件为catalina.log
tail -f catalina.log 该命令用来的查看动态的日志信息
tail -fn 200 catalina.log 查看最后200行日志,且动态显示
tail -f catalina.log | grep '过滤字符串' 显示存在需要过滤的字符串的行的动态的日志信息
2.grep
grep '过滤字符串' catalina.log 从日志文件中过滤出字符串
grep -r '过滤字符串' ./ 从当前目录中的所有文件中过滤出字符串
3.less
less catalina.log 进入日志文件,然后
g 是到文件头部
G 也就是Shift+g,到文件尾部
ctrl+F 向前移动一屏
ctrl+B 向后移动一屏
ctrl+D 向前移动半屏
ctrl+U 向后移动半屏