ip-->mac
先判断两台计算机子网地址是否一样
半连接池限制的是同一时间的请求数而非连接数
名称 | 取值范围 | 存储空间 |
---|---|---|
字节(byte) | -2^7~2^7-1 | 1字节 |
短整数(short) | -2^15~2^15 -1 | 2字节 |
整数(int) | -2^31~2^31 -1 | 4字节 |
长整数(long) | -2^63~2^63 -1 | 8字节 |
单精度(float) | 2^-149~2^128 -1 | 4字节 |
双精度(double) | 2^-1074~2^1024 -1 | 8字节 |
隐藏对象的属性和实现细节,进对外提供公共访问方式
好处: 将变化隔离,减少耦合,提高复用性和安全性
从已有的类中派生出新的类,新的类吸收了已有类的行为和属性并拓展新的能力,但不能选择性的继承
Java中类不支持多继承,接口支持,一个接口可以继承多个其他接口
父类是子类的抽象,子类是父类的具体
通过继承可以提高代码复用性,继承是多态的前提
多态是三大特性中最重要的操作
多态是同一个行为具有多个不同表现形式或形态的能力
多态是同一个接口,使用不同的实例而执行不同操作
两种形式可以实现多态:继承(多个子类对同一方法的重写),接口(实现接口并覆盖接口中同一方法)
编译时多态 | 运行时多态 |
---|---|
编译期间决定目标方法 | 运行期间决定目标方法 |
overloading重载实现 | 同名同参 |
方法名相同,参数名不同 | overriding和继承实现 |
JVM决定目标方法 |
相同 | 不同 | |
---|---|---|
都是上层的抽象 | 抽象类可以包含方法的实现,接口只能包含接口的声明 | |
不能被实例化 | 继承类只能继承一个抽象类,如果继承类为非抽象类,必须实现抽象类所有方法,实现类可以实现多个接口 | |
都可以包含抽象方法,子类必须覆写这些抽象方法 | 抽象级别:接口>抽象类>实现类 | |
作用不同:接口用于约束程序行为,继承则用于代码复用 | ||
字段声明:抽象类任意,接口默认static final | ||
构造器:抽象类可以有,接口不能有 | ||
声明:接口-interface(隐式抽象) , 抽象类-abstract | ||
接口1.8之前 只有静态常量 和抽象方法 1.8加入了默认方法 1.9加入了私有方法 | ||
“/”: 根目录
/bin: 应用程序可执行文件,如常用的二进制命令:ls、cp、mkdir
/boot:Linux内核与系统引导程序目录
/dev:设备文件的目录,比如声卡、磁盘、光驱
/etc: yum、rpm方式安装应用程序的默认配置文件路径
/home: 用户数据
/var:系统与软件服务运行日志目录
/lib:启动系统与运行命令所需的共享库文件与内核模块目录
/proc:系统运行时,进程信息与内核信息存放在此目录
/root:Linux超级用户目录,admin
/sbin:系统管理命令存放目录,是超级用户root可执行命令的存放地
/usr:用户安装的三方应用,包含两个重要子目录
/user/local: 编译方式安装程序的默认目录
/user/src: 程序源码目录
特点:
SSH是专为远程登陆和其他网络服务提供的安全协议
SSH分为两个不兼容版本1.x和2.x,默认通过SSH2.x连接
常见的工具Xshell与SecureCRT
常用于Window远程访问
还支持Telent、RLogin、Serial等其他连接方式
Xftp是Xshell配套组件,用于向服务器上传/下载文件
cd | 切换目录 |
---|---|
pwd | 查看当前目录 |
ls、ll | 显示目录内容 |
mkdir -p -v | 连续创建多级内容并打印信息 |
cp | 复制文件与目录 -r 复制整个文件夹 |
mv | 移动或重命名文件 |
rm -rf | 强制删除文件或目录 多级 |
find | 查找目录或文件 |
clear | 控制台清屏 |
which | 查看可执行文件位置 |
命令 | 用途 |
---|---|
delete or x | 删除 |
dd | 删除整行 |
/str | 全文查找str字符,n下一个,N前一个 |
:% s/old/new/g | 替换文件内所有old字符为new |
u | 撤销最近一次操作 |
:wq or :wq! | 退出并保存,只读文件需要加! |
:q! | 强制退出放弃保存 |
i | 编辑模式 |
home/end | 行首/行尾 |
命令 | 用途 |
---|---|
echo >> | 屏幕打印与文本输出 >重写 >>追加 |
cat -nE(cat >>)(<<abc) | (合并文件)或查看文件内容 显示行号 和空行 (以abc结束输入) |
tail -n 2 看日志 | 查看文件内容尾部最后两行 |
grep abc ahsfi.txt | 文本过滤工具 |
tail -f | 动态监控文本尾部(产生变化) |
ll | grep -E "log[0-9]{1,5}.txt" | 正则表达式模糊查询 详细显示目录(通道) |
tar zcvf 压缩
tar zxvf 文件名 -C 解压缩
选项 | 用途 |
---|---|
z | 通过gzip压缩或者解压 |
c | 创建新的tar.gz文件 |
v | 显示执行过程 |
f | 指定压缩文件名称 |
x | 解压缩tar.gz文件 |
-C | 指定解压缩目录 |
rpm:Red Hat 软件包管理器,相当于应用程序安装文件的执行者
编译安装:下载程序源码进行编译安装
yum通过引入软件仓库,联网下载rpm包及依赖,并依次自动安装
yum search 应用名 | 在仓库中查询是否存在指定应用 |
---|---|
yum install -y 应用名 | 全自动下载安装应用及其依赖 |
yum info 应用名 | 查看应用详细信息 |
yum list installed 应用名 | 查看已安装的应用程序 |
rpm -ql 应用名 | 查看安装后输出的文件清单 |
yum remove -y 应用名 | 全自动卸载指定应用 |
yun localinstall -y | 本地安装 |
yum 下载缓存路径:/var/cache/yum/
rpm需要用户自己解决依赖问题
ifconfig | 查看网卡ip |
---|---|
netstat | 查看网络端口号 |
ps -ef | 查看进程 |
kill -9 PID | 杀掉进程 |
选项 | 用途 |
---|---|
t | 显示tcp传输协议的连接状况 |
u | 显示UDP传输协议的连接状况 |
l | 显示处于监听状态的网络连接 |
p | 显示应用pid和程序名称 |
n | 显示ip地址 |
a | 显示所有连接 |
o | 显示计时器 |
指令 | 用途 |
---|---|
start | 启动服务 |
stop | 停止服务 |
restart | 重启服务 |
enable | 设置开机启动 |
disable | 禁止开机启动 |
status | 查看服务状态 |
daemon-reload | 重载服务配置文件 |
list-unit-files | 列出所有服务 |
命令 | 用途 |
---|---|
useradd | 添加新用户 |
password | 修改密码 |
usermod | 修改用户信息/分配组(覆盖原组) |
groupadd | 创建新的用户组 |
chown | 更改文件的属主或属组 |
chmod | 更改文件的访问权限 |
newgrp | 切换用户当前组 |
visudo | 等同于vi /etc/sudoers 100gg(快速定位) |
sudo |
d | r | W | X | r | w | x | r | w | x |
---|---|---|---|---|---|---|---|---|---|
4 | 2 | 1 | 4 | 2 | 1 | 4 | 2 | 1 | |
目录 | 属主读取 | 属主写入 | 属主运行 | 组读取 | 组写入 | 组运行 | 其他读取 | 其他写入 | 其他运行 |
chomd 777 abc.md
root 目录下 visudo 修改文本 后 visudo -c 解析
firewall-cmd state 查看防火墙状态
firewall-cmd --list-ports 当前防火墙放行端口
firewall-cmd --zone=public --permanent --add-port=8080/tcp 永久放行8080
firewall-cmd --reload 重载防火墙
firewall-cmd --zone=public --permanent --remove-port=8080/tcp 移除8080
firewall-cmd --zone=public --permanent --add-rich-rule="rule family="ipv4" source address=" " port portocol="tcp" port="3306" accept " 限制 3306端口 来自address "
.sh文件
种类 | shell解释器 |
---|---|
Bourne Shell | /usr/bin/sh或/bin/sh |
Bourne Again Shell | /bin/bash(默认) |
C shell | /usr/bin/csh |
K shell | /usr/bin/ksh |
Shell for Root | /sbin/sh |
常见shell https://blog.csdn.net/Dome_/article/details/87866346
flush privileges
jvm就是二进制字节码的运行环境,负责装在字节码到其内部,解释/编译为对应平台的机器指令执行。
一次编译,到处运行
自动管理内存
自动垃圾回收机制
(用户(字节码文件(jvm(操作系统(硬件)))))
定义为一个数字数组,主要用于存储方法参数和定义在方法体内部的局部变量,这些数据包括各类基本数据类型、对象引用,以及returnAddress类型
最基本的存储单元是slot(变量槽),32位以内只占用一个slot,byte、short、char、boolean在存储前被转换为int,long、double占两个slot
线程私有数据,不存在线程安全问题
局部变量表所需容量大小是在编译期确定下来的,在方法运行期间是不会改变局部变量表大小
局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁而销毁
在栈帧中,与性能调优关系最为密切的部分就是局部变量表。在方法执行时,虚拟机使用局部变量完成方法的传递
局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或者间接引用的变量就不会被回收
JVM会为局部变量表中的每一个slot都分配一个访问索引,通过这个索即可成功访问到局部变量表中的局部变量值
当一个实例方法被调用时,它的方法参数和方法体内部定义的局部变量会按照顺序被复制到局部变量表的每一个slot上
如果需要访问局部变量表中一个64bit(占位两个slot) 的局部变量值时,只需要使用前一个索引即可(因为占两个位,使用起始索引)。
如果当前帧是由构造方法或者实例方法(静态方法)创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列
栈帧中的局部变量表中的slot是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很可能会复用过期局部变量的槽位,从而节省资源
又名表达式栈,数组实现
主要用于保存计算结果的中间过程,同时作为计算过程中变量临时存储空间
栈的最大深度在编译器确定 max_stack
操作数栈并非采用索引方式来进行数据访问,而是采用出栈入栈方式来完成一次数据操作
操作数存储在内存中,频繁的执行内存读/写操作必然会影响执行速度。
栈顶缓存技术,将栈顶元素全部缓存在 物理cpu的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率
指向运行时常量池的方法引用
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含着个引用的目的就是为了支持当前方法的代码能够实现动态链接。比如invokedynamic指令
在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件 的常量池里。比如描述一个方法调用了其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
在jvm中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关
被调用的方法在编译器可知,且运行时保持不变,这种情况下将调用方法的符号引用转换为直接引用的过程
被调用的方法在编译器无法被确定下来。只能够在程序运行时将符号引用转换为直接引用。
绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,仅仅发生一次
被调用的方法编译期可知,且运行期间保持不变,即可将这个方法与所属类型进行绑定。由于明确了被调用的目标方法是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用
invokespecial
体现为构造器
被调用的方法在编译器无法被确定下来。只能够在程序运行时根据实际的类型绑定相关的方法
invokeinterface
invokevirtal
invokevirtal 调用所有虚方法(除去final)
invokeinterface
在编译器确定版本,运行时不可变的方法
静态方法、私有方法、final方法、实例构造器、父类方法
类的继承关系 声明是父类 创建的是子类对象
方法重写
invokestatic 调用静态方法 解析阶段确定唯一方法版本
invokespecial 调用方法、私有及父类方法,解析阶段确定唯一方法版本
找到操作数栈顶的第一个元素所执行的实际类型,记作A
如果在类型A中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IlleagalAccessError
否则,按照继承关系从下往上一次对A 的各个父类进行第二步的搜索和验证过程。
如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常
程序试图访问或者修改一个属性或调用一个方法,这个属性或者方法,你没有访问权限。一般会引起编译器异常。这个错误如果发生在运行时,就说明一个类发生了不兼容的改变
方法正常退出或者异常退出的定义
存放调用该方法的pc寄存器的值
方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的吓一条指令的地址。
异常退出时,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息
无论那种方式退出,在方法退出后都返回到该方法被调用的位置。
本质上,方法的退出就是当前栈帧出栈的过程。此时需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置pc寄存器的值等,让方法调用继续下去
正常完成出口和异常完成出口的区别在于:通过异常完成出口的不会给他的上层调用者产生任何的返回值
对程序调试提供支持的信息
当某个线程调用一个本地方法时,就不受虚拟机限制
hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一
-Xms 起始空间 默认 物理电脑内存大小 /64
-Xmx 最大空间 默认 物理电脑内存大小 /4
通常将二者设置相同值,目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分割计算堆区的大小,以提高性能
自适应机制
-XX:UseAdaptiveSizePolicy 关闭自适应
在HotSpot中,Eden空间和另外两个幸存者空间缺省所占的比例是8:1:1
可以通过选项“-XX:SurvivorRatio” 调整空间比例
不同对象的生命周期不同,10%-99%的对象是临时对象
分代唯一理由就是优化GC性能,对特定区域经行扫描,提高效率
对象出生在伊甸区并经过第一次minor gc后仍然存活,并且能够被幸存区容纳的话,将被移动到幸存区中,将年龄置1。该对象每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认15,每个jvm,每个gc都有所不同)时,就会被晋升到老年代
对象晋升老年代的阈值,可以通过 选项 -XX:MaxTenuringThreshold来设置。
针对不同年龄段的对象分配原则如下:
优先分配到伊甸区
大对象直接分配到老年代
尽量避免程序中出现过多的大对象
长期存活的对象分配到老年代
动态对象年龄判断
如果幸存区中相同年龄的所有对象大小的总和大于幸存区空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需达到阈值
空间分配担保
-XX:+PrintFlagsInitial 查看所有参数默认初始值
-XX:+PrintFlagsFinal 查看所有参数的最终值(意味着存在修改)
具体查看某个参数指令:jps 查看当前运行中的进程
jinfo -flag SurvivorRatio 进程id
-XX:-Xms 初始堆空间内存(默认物理内存的1/64)
-XX:-Xmx 最大堆空间内存(默认物理内存的1/4)
-XX:-Xmn 设置新生代的大小(初始值及最大值)
-XX:NewRatio 配置新生代与老年代在堆结构中的占比
-XX:SurvivorRatio 设置新生代伊甸区和s0/s1空间的比例
-XX:MaxTenuringThreshold 设置新生代垃圾的最大年龄
-XX:+PrintGCDetails 打印详细的GC日志
-XX:HandlePromotionFailure 是否设置空间分配担保
如果经过逃逸分析后发现,一个对象并没有逃出方法的话,那么就可能被优化成栈上分配。这样无需要在堆上分配内存,也不需要垃圾回收。这是最常见的堆外存储技术
基于OpenJDK深度定制的TaoBaoVM ,其中创新的GCIH技术实现off-heap,将生命周期较长的Java对象从heap中移至heap外,并且GC无法管理GCIH内部的JAVA对象,以此降低GC的回收频率和 提升GC效率
将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是对分配
常见的栈上分配场景:成员变量赋值、方法返回值、实例引用传递
hotspot 中并未应用栈上分配
如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以考虑不同步。
线程同步的代价是相当高的,同步的后果是降低并发性和性能
在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除
字节码中存在synchronized 运行时消除
有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在cpu寄存器中
标量是指一个无法分解成更小数据的数据。java中的原始数据类型就是标量。
相对的,还可以分解的数据叫聚合量,java的对象就是聚合量,可以分解成其他聚合量和标量
在JIT阶段,如果经过逃逸分析,发现一个对象不能被外界发现,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的成员变量来代替。这个过程就是标量替换。虚拟机默认打开
开发中能使用局部变量的,就不要使用在方法外定义
逃逸分析并不成熟
无法保证逃逸分析的性能消耗一定高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂分析的,这其实也是一个相对耗时的过程。极端例子:分析之后发现没有对象逃逸!
所有的对象实例都是创建在堆上
尽管所有的方法去在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。但对于HotspotJVM而言
,方法区还有一个别名叫做non-heap(非堆),目的就是要和堆分开。所以,方法区看作是一块独立于java堆的内存空间
存储已被虚拟机加载的类型信息、变量、静态常量、即编译后的代码缓存等
对每个加载的类型(Class、interface、enum、注解),jvm必须在方法区中存储一下类型信息:
->
->
->
域名称、类型、修饰符
方法名称
方法返回类型(或者void)
方法参数的数量和类型(按照数量)
方法修饰符
方法的字节码、操作数栈、局部变量表以及其大小(abstract和native)
系统类 启动器加载器加载的核心类,运行期间会用到 java.lang.Class Object HashMap String
java虚拟机调用操作系统方法执行时所引用的java对象
同步锁机制 被加锁的对象
活动线程中所使用的对象 (栈帧中(引用)局部变量所(指向)引用的对象)
强引用是使用最普遍的引用。如果一个对象具有强引用,那么垃圾回收器绝不会回收它。例如:StringBuilder sb = new StringBuilder("test");变量str指向StringBuffer实例所在的堆空间,通过str可以操作该对象。如下:
强引用特点:
软引用是除了强引用外,最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆使用率临近阈值时,才会去回收软引用的对象。因此,软引用可以用于实现对内存敏感的高速缓存。SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。一旦垃圾线程回收该Java对象之后,get()方法将返回null。
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有时候会返回null
//sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。使用软引用能防止内存泄露,增强程序的健壮性。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。当调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。在任何时候,都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,可以检查哪个SoftReference所软引用的对象已经被回收,于是可以把这些失去所软引用的对象的SoftReference对象清除掉。
弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。在java中,可以用java.lang.ref.WeakReference实例来保存对一个Java对象的弱引用。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
虚引用是所有类型中最弱的一个。一个持有虚引用的对象和没有引用几乎是一样的,随时可能被垃圾回收器回收,当试图通过虚引用的get()方法取得强引用时,总是会失败。并且虚引用必须和引用队列一起使用,它的作用在于检测对象是否已经从内存中删除,跟踪垃圾回收过程。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,将这个虚引用加入引用队列。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
//pf.isEnQueued();//返回是否从内存中已经删除
在young GC时会进行 GC root的初始标记
老年代占用堆空间达到阈值时,进行并发标记(不会STW)
-xx:InitiatingHeapOccupancyPercent = percent (默认45%)
会STW
新生代垃圾收集
新生代垃圾收集同时进行并发标记
混合收集 (对新生代老年代幸存区都进行较大规模的收集)等内存收集完成 重新进入新生代垃圾收集过程
三阶段循环
//创建 iterator obj
Collection collection = new ArrayList();
Iterator iterator = collection.iterator() ;
while(iterator.hasnext){
Object obj =iterator.next();
}
//再次遍历
iterator = collection.iterator();
//增强for是迭代器的简化版
Collection collections = new ArrayList();
for(Object col:cllections){
sout(col);
}
Collection extends iterable
元素有序,即添加元素顺序和取出顺序一致,且可重复
每个元素都有其对应索引
迭代 foreach fori
可以加入 null 且多个
数组实现存储
基本等同于vector 线程不安全(效率高)多线程下不建议使用
https://www.jianshu.com/p/58f36f37405a
grow(int minCapacity) 方法实现的功能用一句话概括就是,如果 minCapacity 小于等于原数组的 1.5 倍,则扩容至原数组的 1.5 倍,如果 minCapacity 大于原数组的 1.5 倍,则扩容至 minCapacity
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // newCapacity 是 oldCapacity 的 1.5 倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
transient Object[] elementData; // non-private to simplify nested class access
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
linkedlis底层实现了双线链表和双端队列特点
可以添加任意元素(可重复),包括null
线程不安全,没有实现同步
底层维护了一个双向链表
维护了两个属性first和last分别指向首尾节点
每个节点(node对象),里边又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向最后一个节点。最终实现双向链表
linkedlist的元素的添加和删除,不是通过数组完成的,相对来说效率较高
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
线程同步的,即线程安全。Vector的操作方法带有synchronized
底层也是一个 object数组
在开发中,需要考虑线程同步安全时,使Vector
无参,默认10,满后按两倍扩。
指定大小,则直接两倍扩
无序(插入和取出顺序不一致)没有索引
不允许重复元素,最多包含一个null
Set实现类有:
迭代器
增强for
不能使用fori
实现了set接口
实际上是HashMap
可以存放null值,但是只能有一个null
不保证元素是有序,取决于Hash()后,再确定索引的结果
哈希值不是哈希code 是哈希code再处理后得到return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
加载因子0.75
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//防止数组越界 按位与 同1为1
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//p为数组索引
Node<K,V> e; K k;
//引用相同或者内容相同
//且哈希相同(进入 同一数组索引下的链表)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是红黑树
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断链表是否到达七个节点,达到则树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
添加一个元素时,先得到hash值-会转成-索引值
找到存储数据表table,看到这个索引位置是否已经存放的有元素
如果有,调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后
在java8中,如果一条链表的元素个数超过默认值8,并且table的大小>=64,就会进行树化
linkedhashset底层维护了一个hash表(数组)和双向链表
每一个节点有before和after属性 目的为形成双向链表
在添加一个元素时,先算hash值,在求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表
这样保证插入顺序和遍历顺序一致
boot项目启动时加载主配置类@SpringBootApplication,开启自动配置功能
@EnableAutoConfiguration 中 @Import({AutoConfigurationImportSelector.class}) 下 AutoConfigurationImportSelector类 中的getCandidateConfigurations方法里通过SpringFactoriesLoader.loadFactoryNames()扫描具有META-INF/spring.factories的jar包
(为什么要有spring.factories?:因为我们整个项目里面的入口文件只会扫描整个项目里面下的@Compont @Configuration等注解 但是如果我们是引用了其他jar包,而其他jar包只有@Bean或者@Compont等注解,是不会扫描到的。 除非你引入的jar包没有Bean加载到容器当中 所以我们是通过写/META-INF/spring.factories文件去进行加载的。)
此时获取了所有候选配置(经过去重)。
按需加载配置 , 加载生效的配置类(默认绑定配置文件)就会给容器中装配很多组件
修改默认配置(如果用户自己配置了 以用户的优先,没配置的话默认)
日志级别:error,warn,info,debug,trace
对于系统健壮性的保证 fileter打印每一个请求提高开发和调试的效率
static 变量 @value 无法从配置文件注入
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
不遵循SQL标准。
不支持ACID。
远超于SQL的性能。
对数据高并发的读写
海量数据的读写
对数据高可扩展性的
需要事务支持
基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
(用不着sql的和用了sql也不行的情况,请考虑用NoSql)
默认16个数据库,类似数组下标从0开始,初始默认使用0号库
使用命令 select 来切换数据库。如: select 8
统一密码管理,所有库同样密码。
dbsize查看当前数据库的key的数量
flushdb清空当前库
flushall通杀全部库
Redis是单线程+多路IO复用技术
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
串行 vs 多线程+锁(memcached) vs 单线程+多路IO复用(Redis)
(与Memcache三点不同: 支持多数据类型,支持持久化,单线程+多路IO复用)
keys *查看当前库所有key
exists key判断某个key是否存在
type key 查看你的key是什么类型
del key 删除指定的key数据
unlink key 根据value选择非阻塞删除
仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
expire key 10 10秒钟:为给定的key设置过期时间
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
select命令切换数据库
dbsize查看当前数据库的key的数量
flushdb清空当前库
flushall通杀全部库
二进制安全
一个string value最多可以是512m
incr 将key中存储的数字值增1 只能对数字值操作,空则新增值为1
decr 减一,空则新增值问-1
incrby/decrby <步长> 自定义步长增减
mset .....
同时设置一个或多个 key-value对
mget .....
同时获取一个或多个 value
msetnx .....
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
getrange <起始位置><结束位置> 获得值的范围
setrange <起始位置> 用 覆写所储存的字符串值,从<起始位置>开始(索引从0开始)。
setex <过期时间> 设置键值的同时,设置过期时间,单位秒。
getset 以新换旧,设置了新值同时获得旧值。
String的数据结构为动态字符串,可修改。采用预分配冗余空间的方式来减少内存的频繁分配
当字符串长度小于1m,扩容是加倍现有的空间,如果超过1m,扩容时只会多扩1m的空间,不超过512m
单键多值
底层双向链表,对两端操作性高,通过索引下标操作中间的节点性能很差
lpush/rpush .... 从左边/右边插入一个或多个值。
lpop/rpop 从左边/右边吐出一个值。
rpoplpush 从列表右边吐出一个值,插到列表左边。
lrange 按照索引下标获得元素(从左到右)
lrange mylist 0 -1 0左边第一个,-1右边第一个,(0-1表示获取所有)
lindex 按照索引下标获得元素(从左到右)
llen 获得列表长度
linsert before 在的前面插入插入值
lrem 从左边删除n个value(从左到右)
lset将列表key下标为index的值替换成value
List的数据结构为快速链表quickList。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。
Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变
sadd .....
将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers 取出该集合的所有值。
sismember 判断集合是否为含有该值,有1,没有0
scard返回该集合的元素个数。
srem .... 删除集合中的某个元素。
spop 随机从该集合中吐出一个值。
srandmember 随机从该集合中取出n个值。不会从集合中删除 。
smove value把集合中一个值从一个集合移动到另一个集合
sinter 返回两个集合的交集元素。
sunion 返回两个集合的并集元素。
sdiff 返回两个集合的差集元素(key1中的,不包含key2中的)
Set数据结构是dict字典,字典是用哈希表实现的。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。
Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化(改变对象为可存储或者可传输的状态)和并发修改控制的问题
hset 给集合中的 键赋值
hget 从集合取出 value
hmset ... 批量设置hash的值
hexists查看哈希表 key 中,给定域 field 是否存在。
hkeys 列出该hash集合的所有field
hvals 列出该hash集合的所有value
hincrby 为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。、
当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
zadd …
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zrange [WITHSCORES]
返回有序集 key 中,下标在之间的元素
带WITHSCORES,可以让分数一起和值返回到结果集。
zrangebyscore key minmax [withscores] [limit offset count]
返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore key maxmin [withscores] [limit offset count]
同上,改为从大到小排列。
zincrby 为元素的score加上增量
zrem 删除该集合下,指定值的元素
zcount 统计该集合,分数区间内的元素个数
zrank 返回该值在集合中的排名,从0开始。
SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
zset底层使用了两个数据结构
(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息
Redis客户端可以订阅任意数量的频道
注意:发布的消息没有持久化,订阅后才能收到
redis 使用 Bitmaps 实现位操作
setbit设置Bitmaps中某个偏移量的值(0或1) offset:偏移量从0开始
getbit获取Bitmaps中某个偏移量的值 获取键的第offset位的值(从0开始算)
bitcount[start end] 统计字符串从start字节到end字节比特值为1的数量
统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。
注意:redis的setbit设置或清除的是bit位置,而bitcount计算的是byte位置。
bitop and(or/not/xor) [key…]
bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。
在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog
Redis HyperLogLog 是用来做基数统计(去除重复元素后剩余的数据)的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
pfadd < element> [element ...] 添加指定元素到 HyperLogLog 中
将所有元素添加到指定HyperLogLog数据结构中。如果执行命令后HLL估计的近似基数发生变化,则返回1,否则返回0。
pfcount [key ...] 计算HLL的近似基数,可以计算多个HLL
pfmerge [sourcekey ...] 将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得
GEO 地理信息的缩写,元素的二维坐标,在地图上就是经纬度
redis 基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
geoadd< longitude> [longitude latitude member...] 添加地理位置(经度,纬度,名称)
已经添加的数据,是无法再次往里面添加的。
geopos [member...] 获得指定地区的坐标值
geodist [m|km|ft|mi ] 获取两个位置之间的直线距离
georadius< longitude>radius m|km|ft|mi 以给定的经纬度为中心,找出某一半径内的元素
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 Redis事务的主要作用就是串联多个命令防止别的命令插队。
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。 组队的过程中可以通过discard来放弃组队。
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
取消 WATCH 命令对所有 key 的监视。 如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里。
注:快照是数据存储的某一时刻的状态记录;备份则是数据存储的某一个时刻的副本。
Redis可以通过创建快照来获得存储在内存⾥⾯的数据在某个时间点上的副本。Redis创建快照之后,可 以对快照进⾏备份,可以将快照复制到其他服务器从⽽创建具有相同数据的服务器副本(Redis主从结 构,主要⽤来提⾼Redis性能),还可以将快照留在原地以便重启服务器的时候使⽤
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF默认不开启!当AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)
AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。
正常恢复 修改默认的appendonly no,改为yes 将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir) 恢复:重启redis然后重新加载
异常恢复 修改默认的appendonly no,改为yes 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof--fix appendonly.aof进行恢复 备份被写坏的AOF文件 恢复:重启redis,然后重新加载
appendfsync always 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好 appendfsync everysec 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。 appendfsync no redis不主动进行同步,把同步时机交给操作系统。
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。 no-appendfsync-on-rewrite: 如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能) 如果 no-appendfsync-on-rewrite=no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低) 触发机制,何时重写 Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发 重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。 auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发) auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。 例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB 系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size, 如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。
主进程fork出子进程执行重写操作,保证主进程不会阻塞。
子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。
1).子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。
2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。
备份机制更稳健,丢失数据概率更低。
可读的日志文本,通过操作AOF稳健,可以处理误操作。
比起RDB占用更多的磁盘空间。
恢复备份速度要慢。
每次读写都同步的话,有一定的性能压力。
存在个别Bug,造成恢复不能。
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
调整为一主二仆模式
自定义的redis目录下新建sentinel.conf文件,名字不能错
配置哨兵,填写内容
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
启动哨兵
当主机挂掉,从机中根据优先级别:slave-priority选举产生新的主机。原主机重启后会变为从机
复制延时:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
新主登基:从下线的主服务的所有从服务里面挑选一个从服务,将其转成主服务。
选择的条件依次是:
群仆俯首:挑选出新的主服务之后,sentinel(哨兵)向原主服务的从服务发送slaveof新主服务的命令,复制新master
旧主俯首:当已经下线的服务重新上线时,哨兵会向其发送slaveof命令,让其成为新主的从。
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。
数据库访问压力瞬时增大;redis里面没有出现大量key过期;redis正常运行;redis某个key过期,恰好有大量请求访问使用这个key
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key
public synchronized void test1(){
}
public void test2(){
synchronized (this){
}
}
public synchronized static void test3(){}
public static void test4(){
synchronized (MethodSynchronized.class){
}
}
package DesignPattern;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
import static java.lang.Thread.sleep;
@Slf4j
public class ProducePay {
public static void main(String[] args) {
QueueMessage queueMessage = new QueueMessage(2);
for (int i = 0; i < 3; i++) {
int id=i;
new Thread(()->{
queueMessage.put(new Message(id,"值"+id));
},"生产者"+i).start();
}
new Thread(()->{
while(true){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = queueMessage.take();
}
},"消费者").start();
}
}
@Slf4j(topic = "c.QueueMessage")
class QueueMessage{
private LinkedList<Message> list = new LinkedList<>();
private int capcity;
public QueueMessage(int capcity){
this.capcity=capcity;
}
public Message take(){
synchronized (list){
while(list.isEmpty()){
try {
log.debug("队列为空,消费者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从头部取
Message message = list.removeFirst();
log.debug("已消费消息{}",message);
list.notifyAll();
return message;
}
}
public void put(Message message){
synchronized (list){
//判满
while(list.size()==capcity){
try {
log.debug("队列已满,生产者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.addLast(message);
log.debug("已生产消息{}",message);
list.notifyAll();
}
}
}
final class Message{
private int id;
private Object value;
public Message(int id,Object value){
this.id =id;
this.value=value;
}
public int getId(){
return id;
}
public Object getValue(){
return value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value=" + value +
'}';
}
}
降低并发度
大部分实现基于锁,并提供用来阻塞的方法
修改开销相对于较重 读多写少
内部很多操作采用cas优化,一般可提供较高吞吐量
弱一致性
遍历时弱一致性,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续遍历,这时候内容是旧的
求大小弱一致性,size操作未必是100%准确
读取弱一致性
遍历时如果发生了修改,对于非安全容器来说,使用fail-fast机制让遍历立刻失败抛出ConcurrentModificationException
java.util.concurrnet工具包,JDK1.5出现
Java.Lang.Thread.State
线程状态。 线程可以处于以下状态之一:
所有任务按照顺序进行,当前任务完成方可进行下一任务。
单位时间内,多个任务同时执行(必须多核cpu才能做到)
同一时间段多个任务同时执行(单核cpu就可以做到,并发微观串行,宏观并行)
管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。翻译成Java领域的语言,就是管理类的状态变量,让这个类是线程安全的。
管程对应的英文是Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。
管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同 一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行 JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁 执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方 法在执行时候会持有管程,其他线程无法再获取同一个管程
线程的daemon属性为true表示守护,false表示用户线程,设置守护线程需要再start()之前
ReentrantLock lock = new ReentrantLock();
//上锁
lock.lock();
try{
//代码
//解锁
}finally{
lock.unlock();
}
构造有参公平锁,无参非公平锁
注意虚假唤醒问题 while循环代替for循环
syn关键字解决并发问题
List(String) list =Collections.synchronizedList(new ArrayList);
//synchronizedMap
//synchronizedSortdedMap
//synchronizedSortedSet
public void add(E e){
final Reentrantlock lock = this.lock;
lock.lock();
try{
//总的概括就是 ”写时复制”
Object elements = getArray();
int len = elements.length;
Object[] newElements =Arrays.copyOf(elements,len+1);
newElements[len] = e;
setArray(newElements);
return true;
}finally{
lock.unlock();
}
}
synchronized实现同步的基础:Java中的每一个对象 都可以作为锁;
具体表现:
Callable接口创建线程有返回值,这是Runnable所没有的;
Callable 会抛出异常;
FutureTask构造可以传递Callable
class MyThread implements Runnable{
@Override
public void run() {
}
}
新类 MyThread2 实现 callable 接口
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
FutureTask<Integer> futuretask1 = new FutrueTask<>(new MyThread2());
//也可以这样写
FutureTask<Integer> futuretask2 = new FutrueTask<>(()->{
return 200;
});
new Thread(futuretask2,"name").start();
CountDownLatch允许一个或者多个线程去等待其他线程完成操作。
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行 减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法 之后的语句。
//定义一个数值为6的计数器
CountDownLatch count = new CountDownLatch(6);
//计数器减一,不会阻塞
count.countDown();
//数值减至零时唤醒主线程
count.await();
利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。
//构造方法
//创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。
CyclicBarrier(int parties) ;
//创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。
CyclicBarrier(int parties, Runnable barrierAction) ;
//方法
//等待所有 parties已经在这个障碍上调用了 await 。
int await()
//等待所有 parties已经在此屏障上调用 await ,或指定的等待时间过去。
int await(long timeout, TimeUnit unit)
//返回目前正在等待障碍的各方的数量。
int getNumberWaiting()
//返回旅行这个障碍所需的聚会数量。
int getParties()
//查询这个障碍是否处于破碎状态。
boolean isBroken()
//将屏障重置为初始状态。
void reset()
//Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方法获得许可证,release 方法释放许可
//场景: 抢车位, 6 部汽车 3 个停车位
/**
* Semaphore 案列
*/
public class SemaphoreDemo {
/**
* 抢车位, 10 部汽车 1 个停车位
* @param args
*/
public static void main(String[] args) throws Exception{
//定义 3 个停车位(许可量)
Semaphore semaphore = new Semaphore(1);
//模拟 6 辆汽车停车
for (int i = 1; i <= 10; i++) {
Thread.sleep(100);
//停车
new Thread(() ->{
try {
System.out.println(Thread.currentThread().getName() + "找车位 ing");
//获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "汽车停车成功!");
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + "溜了溜了");
semaphore.release();
}
}, "汽车" + i).start();
}
}
}
BlockingQueue
提供了可阻塞的插入和移除的方法
底层采用数组实现的有界队列实现类
ArrayBlockingQueue
一旦创建,容量不能改变。其并发控制采用可重入锁 ReentrantLock
,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。
ArrayBlockingQueue
默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue
。而非公平性则是指访问 ArrayBlockingQueue
的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue
可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue
,可采用如下代码:
private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);
底层采用单向链表实现的阻塞队列,可以当作无界队列也可以当做有界队列来使用,同样满足 FIFO 的特性,与 ArrayBlockingQueue
相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue
容量迅速增,损耗大量内存。通常在创建 LinkedBlockingQueue
对象时,会指定其大小,如果未指定,容量等于 Integer.MAX_VALUE
。
PriorityBlockingQueue
是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 compareTo()
方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator
来指定排序规则。
PriorityBlockingQueue
并发控制采用的是可重入锁 ReentrantLock
,队列为无界队列(ArrayBlockingQueue
是有界队列,LinkedBlockingQueue
也可以通过在构造函数中传入 capacity
指定队列最大的容量,但是 PriorityBlockingQueue
只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容)。
简单地说,它就是 PriorityQueue
的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException
异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
使用线程池的好处:
int corePoolSize
常驻线程数量
int maximumPoolSize
最大线程数量
long keepAliveTime
, TimeUnit unit
线程存活时间
BlockingQueue<Runnable> workQueue
阻塞队列
ThreadFactory threadFactory
线程工厂
RejectedExecutionHandler handler
拒绝策略
ThreadPoolExecutor.AbortPolicy
:抛出 RejectedExecutionException
来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy
:将任务交给调用者执行,也就是直接在调用execute
方法的线程中运行(run
)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果应用程序可以承受此延迟并且要求任何一个任务请求都要被执行的话,可以选择这个策略。ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy
: 此策略将丢弃最早的未处理的任务请求。引入SpringCloud为eureka提供的starter依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
给eureka-server服务编写一个启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
编写一个application.yml文件,内容如下:
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
引入eureka-client依赖
在yml中配置eureka地址
服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡
用服务名代替IP、端口
启动类中的RestTemplate添加负载均衡注释
@loadbalanced
首先了解两个概念:编译和解释。
编译:将源代码一次性转换成目标代码的过程,类似于全文翻译
解释:将源代码逐条转换成目标代码逐条运行的过程,可理解为同声传译。可能没有整体优化效果好。
在Hotspot(Java虚拟机中的一种)中存在JIT即时编译器,能够捕获程序中的热点代码,编译成机器码缓存起来存入方法区中,当遇到相同的代码时,不必再去使用解释器翻译,直接去找对应的机器码执行。避免解释器重复多次的解释执行。
注意:数组类并不通过类加载器加载,而是由Java虚拟机直接创建,但数组类的元素还是要依靠类加载器进行加载。
其创建过程遵循以下规则:
Java规范中并没有规定 Class 对象的存放位置,对于Hot Spot 虚拟机来说,Class 对象虽然是对象,但却是存放在方法区中。
Class Singleton{
//构造器私有 外部能new
private Singleton{
}
//内部创建静态实例
private final static Singleton instance = new Singleton();
//提供公有静态方法 返回实例对象
public Singleton getInstance(){
return instance;
}
}
Class Singleton{
//构造器私有 外部能new
private Singleton{
}
//内部创建静态实例
private static Singleton instance;
static{
instance = new Singleton();
}
//提供公有静态方法 返回实例对象
public Singleton getInstance(){
return instance;
}
}
结论:和静态变量差不多
Class Singleton{
private Singleton{
}
private static Singleton singleton;
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton;
}
return instance;
}
}
Class Singleton{
private Singleton{
}
private static Singleton singleton;
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton;
}
return instance;
}
}
Class Singleton{
private Singleton{
}
private static volatile Singleton singleton;
public static Singleton getInstance(){
if(singleton == null){ //第一个if为了提高效率,当为null时才进入syn代码块
synchronized (Singleton.Class){
if(singleton == null){ //第二个if为了防止出现多个实例的情况
singleton = new Singleton();
/*
new实例过程
1.分配内存给对象
2.初始化对象
3.设置singleton指向分配的内存空间(此时singleton才!=null)
其中2,3会指令重排
*/
}
}
}
return singleton;
}
}
Class Singleton{
private Singleton{}
private static Class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
优点:这种方式采用了类装载的机制来保证初始化实例只有一个线程
静态内部类在Singleton类装载时不会立即实例化,而是在需要实例化时,调用getInstance()方法才会装载SingletonHolder类,从而完成Singelton的实例化
类的静态属性只会在第一次加载类的时候初始化。所以JVM帮助了我们保证了线程的安全性,在别的类初始化时,别的线程是无法进入的
结论:推荐使用
enmu Singleton{
INSTANCE;
public void sayOk{
System.out.println("ok");
}
}
java.lang.Runtime 饿汉式
工厂模式提供了一种创建对象的最佳方式。我们不关心对象的创建细节,只需要根据不同情况获取不同产品即可
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
在运行期建立和删除原型。
1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),
2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
一些基本部件不会变,而其组合经常变化的时候。
与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。