2022-03-04 (50min)
答:存的是json序列化的数据。(String)
使用SpringCache 结合redis 实现对方法返回对象的缓存。
编写RedisConfig类 继承 CachingConfigurerSupport类。
重写cacheManager方法。
@Configuration
public class RedisConfig extends CachingConfigurerSupport{
/**
* 选择redis作为默认缓存工具
* *SpringBoot2.0以上CacheManager配置方式
* @param redisTemplate
* @return
*/
@Bean
public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
// 设置key为String
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
// 设置value 为自动转Json的Object
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
// 不缓存null
.disableCachingNullValues()
// 缓存数据保存1小时
.entryTtl(Duration.ofHours(1));
RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder
// Redis 连接工厂
.fromConnectionFactory(redisTemplate.getConnectionFactory())
// 缓存配置
.cacheDefaults(defaultCacheConfiguration)
// 配置同步修改或删除 put/evict
.transactionAware()
.build();
return redisCacheManager;
}
}
同时为redistmplate配置了一个bean,设置序列化与反序列化方法
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 值采用json序列化
template.setValueSerializer(jacksonSeial);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
首先对验证码进行验证
@PostMapping("/login")
@IpRequired
public Result login(@RequestBody UserVo userVo, HttpServletRequest request) {
User user = new User();
BeanUtils.copyProperties(userVo, user);
// 验证如果不通过,后台直接抛异常
userService.verifyCode(userVo.getVerKey(), userVo.getCode(),
(String) redisUtil.get(userVo.getVerKey()));
}
@Override
public boolean verifyCode(String verKey, String code, String realCode) throws RuntimeException {
// 验证码是否正确都删除,否则验证错误的验证码会存在redis中无法删除
redisUtil.del(verKey);
if (realCode == null || StringUtils.isEmpty(realCode)) {
throw new RuntimeException("请输入验证码!");
}
if (!code.equalsIgnoreCase(realCode)) {
throw new RuntimeException("请输入正确的验证码!");
}
return true;
}
然后根据用户名跟密码判断用户是否匹配
@Override
public User login(User user) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("uid", "username", "password", "data_status", "nickname", "avatar");
wrapper.eq("username", user.getUsername());
//登录的用户
User login_user = userDao.selectOne(wrapper);
log.debug("login_user:[{}]", login_user.toString());
if (!encoder.matches(user.getPassword(), login_user.getPassword())) {
throw new RuntimeException("用户名或密码不正确,登录失败");
}
if (login_user.isDataStatus() == (MessageConstant.UserDisable)) {
throw new RuntimeException("用户已被禁用,登录失败");
}
}
将用户id与用户名放入payload,使用JWTUtils生成token
HashMap<String, String> payload = new HashMap<>();
payload.put("id", String.valueOf(userDB.getUid()));
payload.put("lastIp", userDB.getLastIp());
payload.put("username", userDB.getUsername());
String token = JWTUtils.getToken(payload);
private static final String SING = "!@*(^*#sfdf&*$asdh$F&^";
public static String getToken(Map<String, String> map) {
//设置过期时间
Calendar instance = Calendar.getInstance();
//默认七天过期
instance.add(Calendar.DATE, 7);
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload键值
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
//令牌过期时间
String token =builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));//签名
return token;
}
Token组成:header.payload.signature(头.负载.签名)
签名的过程,是对头部以及负载内容进行签名,如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务端会判断出新的头部和负载形成的签名,其与JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时使用的密钥的话,得出来的签名也是不一样的。
使用到的是@Cacheable注解,参数有value,key,condition。
缓存的名字为value与key的组合,缓存名字可以使用方法中的参数或者#root.methodName
condition中的语句为真的话缓存,不为真就不缓存。
@Override
@Transactional
@Cacheable(value = {"BlogPage"},
key = "#root.methodName" + "+'['+#queryPageBean.currentPage+']'",
condition = "#queryPageBean.queryString==null")
public Page<BlogVo> findHomePage(QueryPageBean queryPageBean) {
//设置分页条件
Page<BlogVo> page = new Page<>(queryPageBean.getCurrentPage(),
queryPageBean.getPageSize());
QueryWrapper<Blog> wrapper = new QueryWrapper<>();
wrapper.like(queryPageBean.getQueryString() != null,
"content", queryPageBean.getQueryString());
page.setTotal(blogDao.selectCount(wrapper));
page.setRecords(blogDao.findHomePage(queryPageBean));
return page;
}
过期会清理。
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
Redis实际使用的过期数据删除策略:
定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
Redis 内存淘汰机制:
4.0 版本后增加以下两种:
Spring Boot 通过 @EnableAutoConfiguration 开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional 按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖。
SpringBoot启动的时候通过@EnableAutoConfiguration注解找到META-INF/spring.factories文件中的所有自动配置类,并对其加载,这些自动配置类都是以AutoConfiguration结尾来命名的。它实际上就是一个JavaConfig形式的IOC容器配置类,通过以Properties结尾命名的类中取得在全局配置文件中配置的属性,如server.port。
IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 基于动态代理,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
JDK动态代理只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
采用快慢指针法,定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
spring对循环依赖的处理有三种情况:
Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖。
Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:调用spring xml中的init 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
举例:A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了
初始化的第一步(createBeanINstance实例化),并且将自己提前曝光到singletonFactories中。
此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过
ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。
此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。
JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
/*
h = key.hashCode() 为第一步:取hashCode值
h ^ (h >>> 16) 为第二步:高位参与运算
*/
}
HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(即数组下标,使用位运算代替取余操作,加快速度。这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
注意:(n - 1) & hash 是为了获取数组下标。
相关:HashMap 的长度为什么是 2 的幂次方
Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,内存是放不下的,用之前还要先做对数组的长度取模运算,这个数组下标的计算方法是“ (n - 1) & hash”。使用了位运算,所以数组长度用2 的幂次方。
先说put。
简要流程如下:
首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;
如果数组是空的,则调用 resize 进行初始化;
如果没有哈希冲突直接放在对应的数组下标里;
如果冲突了,且 key 已经存在,就覆盖掉 value;
如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;
如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。
get方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
计算key的hash值,找到对应hash值的节点,然后调用equals方法判断对应的key是否相等,如果相等则返回value。
JDK1.7中的ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即ConcurrentHashMap 把哈希桶切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
JDK1.8, ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))。
synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
CAS:全称 Compare and swap,即比较并交换,它是一条 CPU 同步原语(原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断)。是一种硬件对并发的支持,针对多处理器操作而设计的一种特殊指令,用于管理对共享数据的并发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
需要读写的内存值 V
旧的预期值 A
要修改的更新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的)。
synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
作用或者三大特性:
用法:
但构造方法不能使用 synchronized 关键字修饰。
构造方法本身就属于线程安全的,不存在同步的构造方法一说。
使用例子,双重校验锁实现单例模式
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//第一次校验singleton是否为空,
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
//第二次校验singleton是否为空
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
第二次校验是防止二次创建实例,线程A获得锁,生成实例,释放锁。线程B已经通过第一个if,获得锁,继续创建实例,出现创建多个实例的情况。
uniqueInstance 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
底层原理:
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor(每个对象中都内置了一个 ObjectMonitor对象) 的持有权。
在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
对象锁的的拥有者线程使用 monitorexit 指令来释放锁。在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁。
synchronized 修饰的方法有ACC_SYNCHRONIZED 标识,指明该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
不过两者的本质都是对对象监视器 monitor 的获取。
锁计数器存在于对象的ObjectMonitor中,也就是在对象实例中,在内存区域的堆里。
介绍:不安全。查的过程可能查到中间值,需要保证多个线程的同步性。
在更新数据时只会锁住部分数据,不会锁住整个表,读取的时候不能保证读取到最近的更新,只能保证读取到已经顺利插入的值。
面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?
redis
read uncommited (读取未提交)
read commited (读取已提交)
repeatedable read (可重复读,默认隔离级别)
串行化
通过MVCC实现默认隔离级别
当前读(锁定读):
如果执行的是下列语句,就是 锁定读(Locking Reads)
select ... lock in share mode
select ... for update
insert、update、delete 操作
在锁定读下,读取的是数据的最新版本,这种读也被称为 当前读(current read)。锁定读会对读取到的记录加锁:
select … lock in share mode:对记录加 S 锁,其它事务也可以加S锁,如果加 x 锁则会被阻塞
select … for update、insert、update、delete:对记录加 X 锁,且其它事务不能加任何锁
InnoDB 在实现Repeatable Read 时,如果执行的是当前读,则会对读取的记录使用 Next-key Lock ,来防止其它事务在间隙间插入数据
在 Repeatable Read 和 Read Committed 两个隔离级别下,如果是执行普通的 select 语句(不包括 select … lock in share mode ,select … for update)则会使用 一致性非锁定读(MVCC)。并且在 Repeatable Read 下 MVCC 实现了可重复读和防止部分幻读
快照读(非锁定读):
多版本控制 (multi versioning) 就是对非锁定读的实现。如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会去等待行上锁的释放。相反地,InnoDB 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)
隐藏字段,readview,undolog
隐藏字段:
Read View 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
主要有以下字段:
在事务隔离级别 RC 和 RR (InnoDB 存储引擎的默认事务隔离级别)下,InnoDB 存储引擎使用 MVCC(非锁定一致性读),但它们生成 Read View 的时机却不同
索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。
MySQL索引默认数据结构为B+树。
B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。
聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
非聚集索引即索引结构和数据分开存放的索引。
name与age建立联合索引。
全文索引
设置两个指针fast和slow,都指向头节点,一个一次移动两次,一个一次移动一次,如果,有一个时刻,他们两个相遇了,或者fast的next是slow(fast跑到了slow的前面),那么就代表有环。
使用倒排索引。
采用的是 “关键词-文档” 矩阵,关键词与网页文档之间的映射关系是 一个关键词对应多个网页文档。
在这个索引中,Name、Color、Rate 这些字段被称为 filed, iphone 666 plus、blue、middle 这些被称作 Term,而 Term 对应的所有商品的 id 比如 [1, 3] 就是 Posting List。
当用户要查找 Color=blue 的商品时,通过索引三的 Term 和 Posting List 很快就可以找到,目标是 id 为 2 的商品,进而通过索引一找到商品 Name 为 华为 mate 98k。
大数据量太慢,占用资源多。
String。String、Hash、Set、List、SortedSet。
1、String:String是最常用的一种数据类型,普通的key- value 存储都可以归为此类。其中Value既可以是数字也可以是字符串。使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。
2、Hash:Hash 是一个键值(key => value)对集合。Redishash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值。
3、Set:Set是一个无序的天然去重的集合,即Key-Set。此外还提供了交集、并集等一系列直接操作集合的方法,对于求共同好友、共同关注什么的功能实现特别方便。
4、List:List是一个有序可重复的集合,其遵循FIFO的原则,底层是依赖双向链表实现的,因此支持正向、反向双重查找。通过List,我们可以很方面的获得类似于最新回复这类的功能实现。
5、SortedSet:类似于java中的TreeSet,是Set的可排序版。此外还支持优先级排序,维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。
@Caching(
evict = {
@CacheEvict(value = "AdminBlog", key = "#uid"),
@CacheEvict(value = {"BlogPage"}, allEntries = true)
})
public boolean addBlog(AddBlogVo addBlogVo, Long uid) {}
先更新数据库,后删除缓存
在addBlog方法执行之后删除缓存
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。
beforeInvocation属性
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
CMS与G1
CMS(Concurrent Mark Sweep,并发标记清除) 收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。
从名字就可以知道,CMS是基于“标记-清除”算法实现的。CMS 回收过程分为以下四步:
初始标记 (CMS initial mark):主要是标记 GC Root 开始的下级(注:仅下一级)对象,这个过程会 STW,但是跟 GC Root 直接关联的下级对象不会很多,因此这个过程其实很快。
并发标记 (CMS concurrent mark):根据上一步的结果,继续向下标识所有关联的对象,直到这条链上的最尽头。这个过程是多线程的,虽然耗时理论上会比较长,但是其它工作线程并不会阻塞,没有 STW。
重新标记(CMS remark):顾名思义,就是要再标记一次。为啥还要再标记一次?因为第 2 步并没有阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾。
并发清除(CMS concurrent sweep):清除阶段是清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发进行的。
G1(Garbage First)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器。G1设计初衷就是替换 CMS,成为一种全功能收集器。G1 在JDK9 之后成为服务端模式下的默认垃圾回收器,取代了 Parallel Scavenge 加 Parallel Old 的默认组合,而 CMS 被声明为不推荐使用的垃圾回收器。G1从整体来看是基于 标记-整理 算法实现的回收器,但从局部(两个Region之间)上看又是基于 标记-复制 算法实现的。
G1 回收过程,G1 回收器的运作过程大致可分为四个步骤:
初始标记(会STW):仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象。
最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。
清理阶段(会STW):更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的。
不安全
false
1000已经超出Integer缓冲池范围,是两个对象,==比较的是地址,所以不同
可以在主线程中使用join()
public static void test1() throws InterruptedException {
List<Thread> threadSet = new ArrayList<>();
for (int i = 1; i < 10; i++) {
Thread thread = new Thread(() -> {
//线程执行
System.out.println("子线程执行");
});
thread.start();
threadSet.add(thread);
}
for (Thread thread : threadSet) {
thread.join();
}
System.out.println("子线程执行完,主线程继续执行");
}
public static void test2() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
//线程执行
System.out.println("子线程执行");
countDownLatch.countDown();
});
thread.start();
}
countDownLatch.await();
System.out.println("子线程执行完,主线程继续执行");
}
public static void test3() throws Exception {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
//线程执行
System.out.println("子线程执行");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
}
cyclicBarrier.await();
System.out.println("子线程执行完,主线程继续执行");
}
两种常用的:join() 和Executors中的newSingleThreadExecutor()
调用join方法,会调用join(0)方法,当参数为0时,会调用wait方法,使主线程(main)阻塞,
/等待子线程执行完毕后,主线程结束等待,继续执行。
//调用join方法,会调用join(0)方法,当参数为0时,会调用wait方法,使主线程(main)阻塞,
// 等待子线程执行完毕后,主线程结束等待,继续执行。
static ExecutorService executorService = Executors.newSingleThreadExecutor();
static Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1 运行");
}
});
static Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread2 运行");
}
});
static Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread3 运行");
}
});
System.out.println("main开始运行");
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
thread3.join();
System.out.println("main运行结束");
使用Executors中的newSingleThreadExecutor()
创建一个单线程的线程池,也可以达到控制线程执行顺序的目的。
/但是不能保证子线程和主线程的执行顺序
newSingleThreadExecutor()方法创建的线程池是一个基于FIFO(先进先出)的队列
也就是说,当我们依次将thread1,thread2,thread3加入队列中时
实际在就绪状态的只有thread1这个线程,thread2,thread3则会被添加到队列中等待
当thread1执行完毕后,则会按进入队列的先后顺序执行队列中的其他线程。
//使用Executors中的newSingleThreadExecutor()
// 创建一个单线程的线程池,也可以达到控制线程执行顺序的目的。
// 但是不能保证子线程和主线程的执行顺序
//newSingleThreadExecutor()方法创建的线程池是一个基于FIFO(先进先出)的队列
// 也就是说,当我们依次将thread1,thread2,thread3加入队列中时
// 实际在就绪状态的只有thread1这个线程,thread2,thread3则会被添加到队列中等待
// 当thread1执行完毕后,则会按进入队列的先后顺序执行队列中的其他线程。
static ExecutorService executorService = Executors.newSingleThreadExecutor();
System.out.println("main开始运行");
executorService.submit(thread1);
executorService.submit(thread2);
executorService.submit(thread3);
System.out.println("main运行结束");
快照,AOF
前端用nginx,打包为docker镜像,后端jar包
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁的实现:
先更新数据库,后删除缓存
Java 还提供了种弱形式的同步,也就是使用 volatile 关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。
当一个变量被声明为volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。 当其它线程读取该共享变量,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。
原理:内存屏障
内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:
对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。
java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。
volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:
应该是一样的。
1、使用!= 或者 < > 导致索引失效
2、类型不一致导致的索引失效
3、函数导致的索引失效
如:
SELECT * FROM `user` WHERE DATE(create_time) = '2020-09-03';
如果使用函数在索引列,这是不走索引的。
4、运算符导致的索引失效
SELECT * FROM `user` WHERE age - 1 = 20;
如果你对列进行了(+,-,*,/,!), 那么都将不会走索引。
5、OR引起的索引失效
SELECT * FROM `user` WHERE `name` = '张三' OR height = '175';
OR导致索引是在特定情况下的,并不是所有的OR都是使索引失效,如果OR连接的是同一个字段,那么索引不会失效,反之索引失效。
6、模糊搜索导致的索引失效
SELECT * FROM `user` WHERE `name` LIKE '%冰';
当%放在匹配字段前是不走索引的,放在后面才会走索引。
7、NOT IN、NOT EXISTS导致索引失效
1,实例化bean对象,以及设置bean属性;
2,如果通过Aware接口声明了依赖关系,则会注入Bean对容器基础设施层面的依赖,Aware接口是为了感知到自身的一些属性。容器管理的Bean一般不需要知道容器的状态和直接使用容器。但是在某些情况下是需要在Bean中对IOC容器进行操作的。这时候需要在bean中设置对容器的感知。SpringIOC容器也提供了该功能,它是通过特定的Aware接口来完成的。 比如BeanNameAware接口,可以知道自己在容器中的名字。 如果这个Bean已经实现了BeanFactoryAware接口,可以用这个方式来获取其它Bean。 (如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 如果Bean实现了BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。)
3,紧接着会调用BeanPostProcess的前置初始化方法postProcessBeforeInitialization,主要作用是在Spring完成实例化之后,初始化之前,对Spring容器实例化的Bean添加自定义的处理逻辑。有点类似于AOP。
4,如果实现了BeanFactoryPostProcessor接口的afterPropertiesSet方法,做一些属性被设定后的自定义的事情。
5,调用Bean自身定义的init方法,去做一些初始化相关的工作。
6,调用BeanPostProcess的后置初始化方法,postProcessAfterInitialization去做一些bean初始化之后的自定义工作。
7,完成以上创建之后就可以在应用里使用这个Bean了。
自定义注解
jvm内存结构,垃圾回收。详细介绍
堆,虚拟机栈,本地方法栈,程序计数器,方法区(元空间)
初始标记
最终标记
筛选回收
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
O(NlogN),O(N2)
优点:平均性能好,O(nlog2n),2为下标
缺点:不稳定,初始序列有序或基本有序时,时间复杂度降为O(n^2)。
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
在项目的 parent 层,可以通过 dependencyManagement 元素来管理 jar 包的版本,让子项目中引用一个依赖而不用显示的列出版本号
统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,才能保证测试的和发布的是相同的成果,因此,在顶层 pom 中定义共同的依赖关系。同时可以避免在每个使用的子项目中都声明一个版本号,这样想升级或者切换到另一个版本时,只需要在父类容器里更新,不需要任何一个子项目的修改;如果某个子项目需要另外一个版本号时,只需要在 dependencies 中声明一个版本号即可。子类就会使用子类声明的版本号,不继承于父类版本号
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,
Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。
比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。
A先射第一支,然后如果对方射两支,A就射一支,对方射一支,A射两支
CPU飙高 | 死循环
我们有个线上应用,单节点在运行一段时间后,CPU 的使用会飙升,一旦飙升,一般怀疑某个业务逻辑的计算量太大,或者是触发了死循环(比如著名的 HashMap 高并发引起的死循环),但排查到最后其实是 GC 的问题。
(1)使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift + P 快捷键可以按 CPU 的使用率进行排序。
top
(2)再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。
top -Hp $pid
(3)使用 printf 函数,将十进制的 tid 转化成十六进制。
printf "%x\n" tid
386e
(4)然后使用jstack命令定位到,程序的哪一行出了问题
jstack pid |grep tid -A60
update coin
set flag =
case
when zheng then fan
ELSE
zheng
end
;
final、finally、finalize的区别?
final 用于修饰变量、方法和类。
final 变量:被修饰的变量不可变,不可变分为引用不可变和对象不可变,final 指的是引用不可变,final 修饰的变量必须初始化,通常称被修饰的变量为常量。
final 方法:被修饰的方法不允许任何子类重写,子类可以使用该方法。
final 类:被修饰的类不能被继承,所有方法不能被重写。
finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0) 可以阻断 finally 执行。
finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。
一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。
继承:
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
基本上代码块分为三种:Static静态代码块、构造代码块、普通代码块
代码块执行顺序静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块
继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器
共同点 :
区别 :
如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
hashMap就有hashCode相同,equals()不一定相同的情况。哈希冲突
上面讲到锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
偏向锁
一句话总结它的作用:减少统一线程获取锁的代价。在大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得,那么此时就是偏向锁。
核心思想:
如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也就变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程ID等于Mark Word的ThreadID即可,这样就省去了大量有关锁申请的操作。
轻量级锁
轻量级锁是由偏向锁升级而来,当存在第二个线程申请同一个锁对象时,偏向锁就会立即升级为轻量级锁。注意这里的第二个线程只是申请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。
重量级锁
重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。
重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。
注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int workerCountOf(int c) {
return c & CAPACITY;
}
//任务队列
private final BlockingQueue<Runnable> workQueue;
public void execute(Runnable command) {
// 如果任务为null,则抛出异常。
if (command == null)
throw new NullPointerException();
// ctl 中保存的线程池当前的一些状态信息
int c = ctl.get();
// 下面会涉及到 3 步 操作
// 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
if (!isRunning(recheck) && remove(command))
reject(command);
// 如果当前线程池为空就新创建一个线程并执行。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
else if (!addWorker(command, false))
reject(command);
}
jvm将虚拟机分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;
程序计数器:线程私有的,是一块很小的内存空间,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址;
虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;
本地方法栈:线程私有的,保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法;
堆:java堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,在jdk1.8中不存在方法区了,被元数据区替代了,原方法区被分成两部分;1:加载的类信息,2:运行时常量池;加载的类信息被保存在元数据区中,运行时常量池保存在堆中;
之前网易面试被问什么是OOM?为什么会出现OOM?怎么解决?
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
Checked Exception 和 Unchecked Exception 有什么区别?
Checked Exception 即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译 。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException…。
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其子类都统称为非受检查异常,例如:NullPointerException、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)等。
SQL 标准定义了四个隔离级别:
MVCC
ReadView,undolog,隐藏字段
在内部,InnoDB 存储引擎为每行数据添加了三个 隐藏字段:
DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引
user: userid productid productname
product: productid date pay
找到userid=100的7天内的各个商品的总额
一面
Step1:类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
Step2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
内存分配的两种方式:(补充内容,需要掌握)
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
内存分配并发问题(补充内容,需要掌握)
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
Step4:设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
#Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:
Hotspot 虚拟机的对象头包括两部分信息
实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
在锁对象的对象头里面有一个 threadid 字段,
在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,
再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,
执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
Java 还提供了种弱形式的同步,也就是使用 volatile 关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。
当一个变量被声明为volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。 当其它线程读取该共享变量,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。
原理:内存屏障
内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:
对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。
java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。
volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:
public class MemoryBarrier {
int a, b;
volatile int v, u;
void f() {
int i, j;
i = a;
j = b;
i = v;
//LoadLoad
j = u;
//LoadStore
a = i;
b = j;
//StoreStore
v = i;
//StoreStore
u = j;
//StoreLoad
i = u;
//LoadLoad
//LoadStore
j = b;
a = i;
}
}
A线程在插入节点B,B线程也在插入,遇到容量不够开始扩容,重新hash,放置元素,采用头插法,后遍历到的B节点放入了头部,这样形成了环,如下图所示:
二面
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
优点 : 可以让代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
Class clz = Class.forName("java.lang.String");
Class clz = String.class;
String str = new String("Hello");
Class clz = str.getClass();
Class c4= Integer.TYPE;
RU, RC, RR,SERIALIZABLE
串行化就相当于给操作的记录上一个共享锁(读写锁),即当读某条记录时就占用这条记录的读锁,此时其它事务一样可以申请到这条记录的读锁来读取,但是不能写(读锁被占的话,写锁就不能被占;读锁可以被多个事务同时占有)
幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读的重点是修改,幻读的重点在于新增或者删除。
数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。
深度优先遍历指的是,从树的根节点开始,先遍历左子树,然后遍历右子树。
Dockerfile指令说明简洁版:
FROM
构建镜像基于哪个镜像
MAINTAINER
镜像维护者姓名或邮箱地址
RUN
构建镜像时运行的指令
CMD
运行容器时执行的shell环境
VOLUME
指定容器挂载点到宿主机自动生成的目录或其他容器
USER
为RUN、CMD、和 ENTRYPOINT 执行命令指定运行用户
WORKDIR
为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录,就是切换目录
HEALTHCHECH
健康检查
ARG
构建时指定的一些参数
EXPOSE
声明容器的服务端口(仅仅是声明)
ENV
设置容器环境变量
ADD
拷贝文件或目录到容器中,如果是URL或压缩包便会自动下载或自动解压
COPY
拷贝文件或目录到容器中,跟ADD类似,但不具备自动下载或解压的功能
ENTRYPOINT
运行容器时执行的shell命令
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
FROM:
定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。
RUN:
用于执行后面跟着的命令行命令。有以下俩种格式:
RUN <命令行命令>
# <命令行命令> 等同于,在终端操作的 shell 命令。
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
COPY
复制指令,从上下文目录中复制文件或者目录到容器里指定路径。
COPY hom* /mydir/
COPY hom?.txt /mydir/
ADD
ADD 指令和 COPY 的使用格类似(同样需求下,官方推荐使用 COPY)。功能也类似,不同之处如下:
CMD
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
CMD
CMD ["<可执行文件或命令>","" ,"" ,...]
CMD ["" ,"" ,...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
ENTRYPOINT
类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。
但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
ENTRYPOINT ["" ,"" ,"" ,...]
可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。
示例:
假设已通过 Dockerfile 构建了 nginx:test 镜像:
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
ENV
设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
ENV
ENV = =...
ARG
构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。
构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。
格式:
ARG <参数名>[=<默认值>]
VOLUME
定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
作用:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
EXPOSE
仅仅只是声明端口。
作用:
EXPOSE <端口1> [<端口2>...]
CPU飙高 | 死循环
我们有个线上应用,单节点在运行一段时间后,CPU 的使用会飙升,一旦飙升,一般怀疑某个业务逻辑的计算量太大,或者是触发了死循环(比如著名的 HashMap 高并发引起的死循环),但排查到最后其实是 GC 的问题。
(1)使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift + P 快捷键可以按 CPU 的使用率进行排序。
top
(2)再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。
top -Hp $pid
(3)使用 printf 函数,将十进制的 tid 转化成十六进制。
printf "%x\n" 14446
386e
(4)然后使用jstack命令定位到,程序的哪一行出了问题
jstack 14445 |grep 386e -A60
JWT 的三个部分依次如下。
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{
"alg": "HS256",
"typ": "JWT"
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
最后,将上面的 JSON 对象使用 Base64URL 算法)转成字符串。
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据。
signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值。
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。以下是详细的唤醒方法:
sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。(暂停线程,不会释放锁)
2.suspend() 和 resume() 方法:
挂起和唤醒线程,suspend()使线程进入阻塞状态,只有对应的resume()被调用的时候,线程才会进入可执行状态。(不建议用,容易发生死锁)
会使得线程放弃当前分得的cpu时间片,但此时线程仍然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。调用yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知)
4.wait() 和 notify() 方法
两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁住的对象,然后再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)
5.join()方法
也叫线程加入。是当前线程A调用另一个线程B的join()方法,当前线程转A入阻塞状态,直到线程B运行结束,线程A才由阻塞状态转为可执行状态。
以上是Java线程唤醒和阻塞的五种常用方法,不同的方法有不同的特点,其中wait() 和
notify()是其中功能最强大、使用最灵活的方法,但这也导致了它们效率较低、较容易出错的特性,因此,在实际应用中应灵活运用各种方法,以达到期望的目的与效果!
不是自增主键,如果换数据库可能会用不到
分库分表,使用了雪花算法生成主键
数据库邮箱字段加唯一索引
https://www.zhangshilong.cn/work/88878.html
雪花算法生成主键。
缺点:在获取时间的时候,可能会出现时间回拨的问题,就是服务器上的时间突然倒退到之前的时间
解决:
当时间回拨较小时,等待时钟同步到最后一次主键生成的时间后再继续工作。
当时间回拨较大时,可以对序列化的初始值设置步长,每次触发时钟回拨事件,则其初始步长就加1w,可以在下面代码的第85行来实现,将sequence的初始值设置为10000。
可以解决。
如果我们把负载因子设置成1,容量使用默认初始值16,那么表示一个HashMap需要在"满了"之后才会进行扩容。那么在HashMap中,最好的情况是这16个元素通过hash算法之后分别落到了16个不同的桶中,否则就必然发生哈希碰撞。而且随着元素越多,哈希碰撞的概率越大,查找速度也会越低。
如果负载因子设置为0.5,那么就会频繁的扩容,浪费空间
当红黑树中的元素减少并小于一定数量时,会切换回链表
undo log。undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子
spring事务的实现AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务
spring事务底层实现使用了代理,aop,通过jdk的动态代理或者cglib,生成了代理类,在代理类中实现了事务功能,如果方法被final修饰,无法重写该方法,也就无法添加事务的功能了
在同一个类的service中,调用其他的事务方法,由于spring的事务实现是因为aop生成代理,这样是直接调用了this对象,所以也不会生成事务。
解决:
在使用spring事务的时候,对象要被spring进行管理,也就是需要创建bean,一般我们都会加@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。,如果忘记加了,也会导致,事务的失效
但是事务方法在另一个线程里面调用,这样会导致两个方法在不同的一个线程中,获取的数据库连接也不一样,所以会是两个不同的事务。
看过spring源码,我们可以知道,spring的事务是通过连接数据库来实现的,当前线程保存了一个map,key—数据源,value----数据库连接,事务其实就是指向同一个连接的,只有拥有同一个数据库连接才能同时提交和回滚,如果在不同的线程,数据库的连接不是同一个,所以事务也不是同一个。
MyISAM,可能一些老的项目还在使用,但是他是不支持事务的
如果创建的不是springboot项目可能会导致这样的问题出现,因为springboot项目有自动装配的类DataSourceTransactionManagerAutoConfiguration,已经默认开启了事务,配置spring.datasource参数就行,如果是spring项目,需要在applicationContext.xml配置的,不然事务不会生效
使用了没有事务的事务传播特性
可能是我们在写代码的时候自己在代码手动进行了try…catch
@Transactional
public void query(Demo demo) {
try {
save(demo);
} catch (Exception e) {
System.out.println("异常");
}
}
这种情况下,spring事务不会进行回滚,因为我们进行了手动捕获异常,然后没有手动抛出,如果想要spring事务的正常回滚,必须抛出它能处理的异常,如果没有抛出异常,spring会认为程序没有问题。
放入不同的类中
在浏览器中输入 url 地址 ->> 显示主页的过程
HTTP/1.0 默认使用短连接 ,也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个TCP连接,这样就会导致有大量的“握手报文”和“挥手报文”占用了带宽。
为了解决 HTTP/1.0 存在的资源浪费的问题, HTTP/1.1 优化为默认长连接模式 。 采用长连接模式的请求报文会通知服务端:“我向你请求连接,并且连接成功建立后,请不要关闭”。因此,该TCP连接将持续打开,为后续的客户端-服务端的数据交互服务。也就是说在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
扩容过程:
因此,table中的元素只有两种情况:
元素hash值第N+1位为0:不需要进行位置调整
元素hash值第N+1位为1:调整至原索引的两倍位置
扩容或初始化完成后,resize方法返回新的table
相比于set,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。
zset有两种不同的实现,分别是zipList和skipList。
zipList:
满足以下两个条件:
skipList:
不满足以上两个条件时使用跳表(组合了hash和skipList)
Redis 中 zset 不是单一结构完成,是跳表和哈希表共同完成
实现方式:Redis sorted set的内部使用HashMap和跳跃表(skipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
score
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程
切换内核态堆栈(由于进程切换需要陷入内核态,用户态到内核态的切换,必然会涉及到内核态堆栈的切换)
PCB的切换(重新引起调度时,操作系统会找到新的进程的PCB,并完成该进程与新进程PCB的切换,寄存器的值,硬件上下文)
—ip(instruction pointer):指向当前执行指令的下一条指令
—bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
—sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
—cr3:页目录基址寄存器,保存页目录表的物理地址
切换页表全局目录(由于进程是独享内存空间的,进程对内存的操作涉及到虚拟内存,页表是实现方式,所以切进程内存,就是切换页表)
4、刷新TLB(TLB本质是一种cache,用于完成虚拟地址和物理地址间的转换)
上下文切换包括保存当前任务的运行环境,恢复将要运行任务的运行环境。当进程被切换时,操作系统内核必须先保护现场,即将处理机状态信息保存在相应的PCB中,以便在该进程重新执行时能再从断点继续执行。然后恢复另一个进程的状态,当前运行任务转为就绪(或者挂起)状态,另一个被选定的就绪任务成为当前任务
合并区间
岛屿数量
当客户端发起的TCP第⼀次握手SYN 包,在超时时间内没收到服务端的ACK,就会在超时重传SYN数据包,每次超时重传的RTO是翻倍上涨的,直到 SYN 包的重传次数到达 tcp_syn_retries值后,客户端不再发送 SYN 包。
当TCP第⼆次握⼿ SYN、ACK 包丢了后,客户端 SYN 包会发生超时重传,服务端SYN、ACK 也会发生超时重传。 客户端SYN包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是5次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是5次。
在建立TCP连接时,如果第三次握手的ACK,服务端无法收到,则服务端就会短暂处于SYN_RECV 状态,而客户端会处于ESTABLISHED状态。 由于服务端⼀直收不到TCP第三次握⼿的ACK,则会⼀直重传SYN、ACK包,直到重传次数超过tcp_synack_retries 值(默认值5次)后,服务端就会断开 TCP连接。
而客户端则会有两种情况
TCP 的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
堆
瞎扯
堆的话只要弹出顶部元素即可,红黑树需要查询
复习linux命令
Redis为什么快
可以 基于 Redis 的优先级队列
压缩表或跳表
ziplist,即压缩列表
zipList:
满足以下两个条件:
压缩列表是由连续性内存组成的顺序性数据结构,一个压缩列表可以包含任意多的entry,每个entry可以保存一个字节数组或者一个整数。
压缩列表在表头有三个字段:zlbytes,zltail,zllen分别表示列表长度(整个列表占用的字节数),列表尾的偏移量(尾节点距离起始地址的字节数)和列表中entry的个数。
列表表尾还有一个zlend,表示列表结束了。
跳表的查找会从顶层链表的头部元素开始,然后遍历该链表,直到找到元素大于或等于目标元素的节点,如果当前元素正好等于目标,那么就直接返回它。如果当前元素小于目标元素,那么就垂直下降到下一层继续搜索,如果当前元素大于目标或到达链表尾部,则移动到前一个节点的位置,然后垂直下降到下一层。
Redis数据结构之Zset
explain的结果
id相同的为一组,组内的按出现顺序执行,id越大的组优先执行。
table:查询的表
possible_keys:可能的索引
key:实际使用的索引
rows:mysql估计要扫描的行数
type:显示使用了何种查询,效率从高到低为system>const>eq_ref>ref>range>index>all
system,const mysql能将查询优化成常量,例如
eq_ref:使用了唯一索引。主键索引、唯一索引
ref:非唯一索引
range:使用了between and,>,<,in
index:扫描所有索引行
all:扫描所有数据行,全表扫描
extra:一些额外关键信息。
using index:使用了覆盖索引
using filesort:索引创建数据排序顺序不符合要求,在外部重新排序
using temporary:使用了临时表来保存信息
using where:使用了where
using join buf:关联表未使用索引,且需要缓存来保存中间结果
mysql 慢查询原因 排查 优化
sleep的线程是处于TIMED_WAITING状态
设置守护线程,定期检查resdis锁是否存在,如果存在就续约,不存在表示任务已完成
参考顺丰一面
只用ac,只有a走了索引。
不包含最左侧的a不走索引
mysql 联合索引 复合索引(abc)如何索引命中规则实测
自动装箱过程是通过调用包装类valueOf()方法实现的,而自动拆箱过程是通过调用包装类 xxxValue()方法实现的(xxx代表对应的基本数据类型,如intValue()、doubleValue()等)
隐式装箱拆箱原理是编译器实现
权限修饰符的总结:
权限修饰符的大小依次是public protected default private
它们的大小指的是可以被调用的范围。
值传递
类加载过程详解
ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))
synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
在HashMap存储自定义对象的时候,需要自己再自定义的对象中重写其hashCode()方法和equals方法,才能保证其存储不重复的元素,否则将存储多个重复的对象,因为每new一次,其就创建一个对象,内存地址是不同的
有俩种可能:
1、如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
2、如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是 AbortPolicy
拒绝策略
1、AbortPolicy
直接丢弃任务,抛出RejectedExecutionException异常,是默认策略
2、CallerRunsPolicy
只用调用者所在的线程处理任务
3、DiscardOldestPolicy
丢弃等待队列中最旧的任务,并执行当前任务
4、DiscardPolicy
直接丢弃任务,但不抛出异常
1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
ThreadLocal
一、破坏请求和保持条件
• 方法一 —— 破坏“请求”条件(预先静态分配法)
每个进程执行之前,必须一次性地申请其在整个运行期间所需的全部资源,全部申请到了才能运行。这样它在整个运行过程中便不会再提出资源请求,从而破坏了“请求”条件。
缺点:
① 资源利用率很低:有些资源可能在最后才会用到,它却一直占用了那么久
② 进程可能出现饥饿现象:可能由于个别资源别其他进程占用而导致某进程迟迟不能开始
• 方法二 —— 破坏“保持”条件
每个进程提出申请资源前必须释放已占有的一切资源
二、破坏非抢占条件
• 方法一 —— 走不通就放弃自己的已有资源造福别人
进程 Pi 申请 Rj 类资源时,检查 Rj 中有无可用资源:有则分配给 Pi ;否则将 Pi 占有的资源全部释放而进入等待状态(Pi等待其原占有的所有资源和申请的资源)
• 方法二 —— 走不通先去抢别人的(前提是别人也走不通),抢不到就放弃自己的
当进程 Pi 申请 Rj 类型的资源时检查 Rj 中有无可用资源:有则分配给 Pi ;否则检查已获得 Rj 类资源的进程 Pk ,若 Pk 处于等待资源状态,则抢占 Pk 的 Rj 类资源并分配给 Pi,若 Pk 不处于等待资源状态,则置 Pi 于等待资源状态(此时Pi原已占有的资源可能被抢占)
这两种方法的缺点:
① 有的资源是不可抢占资源,比如打印机,被抢占后可能导致前一阶段的工作失效
② 延长了周转时间,降低了系统吞吐量,增加了系统开销:因为某些进程的执行可能会被无限推迟
三、破坏循环等待条件(有序资源使用法)
给系统中的所有资源类型进行排序编号
• 每个进程只能按递增顺序申请资源,即进程申请了序号为 8 的资源后,下次只能申请序号为 9 或以上资源
• 如果进程需要同一资源类型的多个实例(也就是序号相同的资源),则必须对它们一起进行申请
• 如果进程后面又想申请序号低的资源(比如5),那就必须把现在拥有的序号为5及其以上的资源全部释放
为什么这种规则可以破坏循环等待条件?
核心: 每个进程只能按递增顺序申请资源
因此每个时刻总有一个进程占据了较高序号的资源,那么它后面继续申请的资源一定是空闲的,这就保证了进程是可以一直向前推进的
优点:
与前两种策略相比,其资源利用率和系统吞吐量都有明显的改善
缺点:
① 序号必须相对稳定,这就限制了新设备的增加
② 如果作业使用各类资源的顺序与系统规定的递增顺序不符合的话,就会造成资源的浪费
③ 按规定次序申请资源的方法会限制用户简单、自主地编程
垃圾回收器
引用计数法
可达性分析
引用计数法很难解决对象之间相互循环引用的问题
可使用下述命令查看虚拟机参数
java -XX:+PrintCommandLineFlags -version
结果
-XX:InitialHeapSize=257905536
-XX:MaxHeapSize=4126488576
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
UseParallelGC指的是Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1
什么是自动装配:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能
@EnableAutoConfiguration:实现自动装配的核心注解
EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。
Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖
重磅!Spring Boot 2.7 正式发布,一大波新特性,看完我彻底躺平了。。
dns 三次握手四次挥手,http请求,浏览器渲染
QUIC(Quick UDP Internet Connection)
双向链表加hashmap
不能,因为双亲委派模型,会让bootstrapclassloader加载类
BootStrapClassLoader的实例范围在sun.boot.class.path中
ExtClassLoader的实例范围在java.ext.dirs中
AppClassLoader的实例范围在java.class.path中
有,i++不是原子操作
锁
使用atomicInteger类
其底层主要运用了unsafe类和cas思想(循环比较并替换).
线程池好处
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
布隆过滤器。
bit数组(BitSet?)
建立小顶堆,10000,然后添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的10000个数就是所需的最大的10000个。建堆时间复杂度是O(mlogm),算法的时间复杂度为O(nmlogm)(n为10亿,m为10000)
堆排序更好
初始堆内存
-Xms<heap size>[unit]
最大堆内存
-Xmx<heap size>[unit]
新生代内存
-XX:NewSize=<young size>[unit] 最小
-XX:MaxNewSize=<young size>[unit] 最大
-Xmn256m 最小与最大相同
新生代与老年代比值 老年除以新生代
-XX:NewRatio = 1
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
串行垃圾收集器
并行垃圾收集器
CMS垃圾收集器
G1垃圾收集器
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseParNewGC
-XX:+UseG1GC
-Xss: 设置线程的最大栈空间,栈空间越大,方法的递归深度越大
-XX:+PrintGCDetails 打印GC详细信息(JDK8,9,10建议使用-Xlog:gc*)
List<Long> longList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
List<String> strings = new LinkedList<>();
longList.add(1L); integerList.add(1); strings.add("a");
System.out.println(longList.getClass() == integerList.getClass()); System.out.println(longList.getClass() == strings.getClass());
true false
longList与integerList都是ArrayList
strings是LinkedList
select * from tablename limit index,pageNum;
select * from tablename limit pageNum offset index;
假设数据表的id是连续递增的,则我们根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and 来查询:
select * from orders_history where type=2
and id between 1000000 and 1000100 limit 100;
select * from orders_history where id >= 1000001 limit 100;
只走a索引,b走不到
mysql 会一直向右匹配直到遇到范围查询 (>、<、between、like) 就停止匹配(包括 like ‘陈%’ 这种)
input
7
3 3 4 7 5 6 8
output
4
2 3 5 6
输入数组长度及数组,找到连续递增的最长子序列长度以及下标数组
@Autowired注解按照类型去IOC容器中查找bean对象,如果容器(即为IOC容器)中没有该类型对象(例如,该类型是个接口),则去容器中查找该类型的子类(实现类)的对象,进行注入
当使用该注解时,Spring容器自动匹配bean(类对象),首先根据class后面的全限定名进行自动匹配(byType方式);其次就是根据id属性进行自动匹配,id的值是setXx方法中的Xx(byName方式)。
它和@Resource注解的原理相反,@Resource注解先通过byName的方式查找,再通过byType的方式,如果都不成功就会报错。
使用@Component等派生注解
@Bean定义方式
@Data
public class ConfigDemoBean {
}
@Configuration
public class BeanLoadConfig {
@Bean
public ConfigDemoBean configDemoBean() {
return new ConfigDemoBean();
}
}
@ComponentScan
/**
* @Description: Springboot 启动类
*/
@ComponentScan(basePackages ={"com.third.bean"})
@SpringBootApplication()
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
@Import注解
@Import(value= com.third.bean.ThirdComponentBean.class)
@SpringBootApplication()
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
spring.factories
我们只需要在将配置放在第三方jar指定的文件中即可,使用者会自动加载,从而避免的代码的侵入
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.third.bean.ConfigurationBean
SpringBoot引入第三方jar的Bean的三种方式
三级缓存解决循环依赖
不能解决构造器的循环依赖
不能解决 prototype 作用域循环依赖
不能解决多例的循环依赖
Spring 循环依赖及三级缓存
MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要开启二级缓存
开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询
当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库
MyBatis 二级缓存全详解
MyBatis处理 #{ } 占位符,使用的 JDBC 对象是PreparedStatement 对象,执行sql语句的效率更高。
使用PreparedStatement 对象,能够避免 sql 注入,使得sql语句的执行更加安全。
#{ } 常常作为列值使用,位于sql语句中等号的右侧;#{ } 位置的值与数据类型是相关的。
MyBatis处理 ${ } 占位符,使用的 JDBC 对象是 Statement 对象,执行sql语句的效率相对于 #{ } 占位符要更低。
${ } 占位符的值,使用的是字符串连接的方式,有 sql 注入的风险,同时也存在代码安全的问题。
${ } 占位符中的数据是原模原样的,不会区分数据类型。
${ } 占位符常用作表名或列名,这里推荐在能保证数据安全的情况下使用 ${ }。
缓存之外,也经常用来做分布式锁,甚至是消息队列
Redis 5.0 新增加的一个数据结构 Stream 可以用来做消息队列,Stream 支持:
发布 / 订阅模式
按照消费者组进行消费
消息持久化( RDB 和 AOF)
Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向
RDB与AOF
不会变
线程不安全:在随机调度之下,线程执行有多种可能,其中某些可能会导致代码出bug就称为线程不安全。
线程不安全的原因
操作系统的随机调度/抢占式执行(万恶之源)–>无法改变
多个线程修改同一个变量(一个字都不能少)—>尽量避免
有些修改操作,不是原子的!(不可拆分的最小单位,就叫原子 即对应一条机器指令)—>通过加锁操作,把指令打包
内存可见性问题(内存改了,但是在优化的背景下,读不到,看不见)
如:线程1一直在读取硬盘上的资源再判断,在多次读取硬盘并获得相同的结果后编译器会认为这样的做法过于低效,然后就会省略读取的过程,一直判断,若此时有线程2需要这个判断结果(读取到数据后的判断结果)就会出现问题.
指令重排序(也是编译器,操作系统等的优化,调整了代码的执行顺序)
用堆做阻塞队列 PriorityBlockingQueue
通过FutureTask类实现,通过实现Callable接口,使用FutureTask启动子线程,通过FutureTask的get()方法即可精准的获取返回值
FutureTask的get方法可以设置超时时间
@Transactional
编程式事务管理使用TransactionTemplate
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional 注解的方式,便可以将事务规则应用到业务逻辑中。
声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别
Java8之后,取消了整个永久代区域,取而代之的是元空间。运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中
jps: 跟linux的ps一样,只不过是列出java程序
jps -m 列出所有java程序,并显示传入参数
jps -l 列出所有java程序,显示类的全限名
jstat:观察java程序运行时的相关信息,主要是堆信息
jstat -class -t pid 1000 2 查看classLoader相关信息,每隔一秒执行一次,总共收集两次
jstat -gc pid 查看gc情况
jstat -gcnew pid 查看新生代的详细信息
jstat -gcold pid 查看老年代的详细信息
jinfo:查看java应用程序的扩展参数,部分参数可支持动态修改
jinfo -flag MaxtenuringThreshold pid 查看gc升级年龄
jinfo -flag +PrintGCDetails pid 修改使用PrintGCDetails参数
jmap:导出堆到文件
jmap -histo pid > /usr/local/tmp/a.txt java程序的对象统计信息
jmap -dump:format=b file=/usr/local/heap.hprof PID 得到java程序的当前快照,
主要用于分析线程的运行情况
jstack:查看线程堆栈
jstack -l pid > /usr/local/tmp/stack.hprof 这里-l是打印锁的详细信息然后
输出到指定目录的*.hprof文件中
基本类型与包装类型
hash值用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash”
取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率
可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低
大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC
YGC时,对Eden区和From Survivor区的存活对象进行处理,如果满足动态年龄判断的条件或者To Survivor区空间不够则直接进入老年代,如果老年代空间也不够了,则会发生promotion failed,触发老年代的回收。否则将存活对象复制到To Survivor区。
此时Eden区和From Survivor区的剩余对象均为垃圾对象,可直接抹掉回收。
此外,老年代如果采用的是CMS回收器,为了减少CMS Remark阶段的耗时,也有可能会触发一次YGC
当晋升到老年代的对象大于了老年代的剩余空间时,就会触发FGC(Major GC),FGC处理的区域同时包括新生代和老年代。除此之外,还有以下4种情况也会触发FGC:
Serial、SerialOld、ParallelScavenger、ParNew、CMS在物理和逻辑上都分代。G1只在逻辑上分代,物理上不分代。ZGC、Shenandoah逻辑和物理上都不分代。Epsilon是jdk11提出debug使用的,不用考虑
Serial、ParallelScavenge、ParNew是用于年轻代的,SerialOld、ParallelOld、CMS是用于老年代的。所以产生了垃圾回收器几种常见的组合:Serial+SerialOld、ParallelScavenger+ParallelOld、ParNew+CMS
新生代一般标记复制,老年代一般标记整理
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
索引跟数据一起存放
只能有一个 聚簇索引的顺序就是数据的物理存储顺序
双向链表
不会,update是当前读,会读取最新记录
因为hashcode和equals都不重写,则存放对象时调用的是Object对象的这两个方法。
那么hashcode就是地址值,而equals也是比较的地址值
SSL本身就是一个协议
不是,第一次是,后面是对称加密
继承Thread,重写Run方法
实现Runnable,重写Run方法
实现Callable,封装FutureTask对象,注入Thread
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){ //外层的if判断:如果实例被创建直接return,不让线程再继续竞争锁
//在没有创建实例时,多个线程已经进入if判断了
//一个线程竞争到锁,其他线程阻塞等待
synchronized (Singleton.class) {
//内层的if判断,目的是让竞争失败的锁如果再次竞争成功的话判断实例是否被创建,创建释放锁return,没有则创建
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
public enum Singleton4 {
INSTANCE;
}
在类加载的时候就创建实例
这种方式是满足线程安全的(JVM内部使用了加锁,即多个线程调用静态方法,只有一个线程竞争到锁并且完成创建,只执行一次)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
public class Singleton3 {
private Singleton3() {
}
private static class Holder {
private static final Singleton3 INSTANCE = new Singleton3();
}
public static final Singleton3 getInstance() {
return Holder.INSTANCE;
}
}
public class Singleton5 {
private static Map<String, Object> objectMap = new HashMap<>();
public static void registerService(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}
telnet 10.119.229.205 11201
netstat -anp |grep 3306
21:FTP服务所开放的端口,用于上传、下载文件。
22:SSH端口,用于通过命令行模式远程连接Linux服务器或vps。
23:Telnet端口,用于Telnet远程登录服务器。
25:SMTP服务所开放的端口,用于发送邮件。
80:HTTP用于HTTP服务提供访问功能,例如,IIS、Apache、Nginx 等服务。您可以参阅、检查、TCP 80端口是否正常工作,排查80端口故障。
110:POP3用于POP3 协议,POP3 是电子邮件收发的协议。
143:IMAP用于IMAP(Internet Message Access Protocol)协议,IMAP 是用于电子邮件的接收的协议。
443:HTTPS 用于HTTPS服务提供访问功能。HTTPS 是一种能提供加密和通过安全端口传输的一种协议。
1433:SQL Server SQL Server的TCP 端口,用于供SQL Server对外提供服务。
1434:SQL Server SQL Server的UDP端口,用于返回SQL Server使用了哪个 TCP/IP 端口。 1521:Oracle通信端口,服务器上部署了Oracle SQL需要放行的端口。
3306:MySQL数据库对外提供服务的端口。
3389:Windows Server Remote Desktop Services Windows Server Remote Desktop Services(远程桌面服务)端口,可以通过这个端口远程连接服务器
8080:代理端口,同80端口一样,8080 端口常用于WWW代理服务,实现网页浏览。如果用了8080端口,访问网站或使用代理服务器时,需要在 IP 地址后面加上 :8080。安装Apache Tomcat服务后,默认服务端口为8080。
137、138、139 NetBIOS协议137、138 为UDP端口,通过网上邻居传输文件时使用的端口。139通过这个端口进入的连接试图获得 NetBIOS/SMB 服务。NetBIOS协议常被用于Windows文件、打印机共享和Samba。
重排链表
DNS解析过程详解
== 对于基本类型和引用类型的作用效果是不同的:
equals() 方法存在两种使用情况:
Hashtable
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentHashMap
ConcurrentSkipListMap
ConcurrentLinkedQueue
New 新建状态(线程刚被创建,start方法之前的状态)
Runnable 运行状态(得到时间片运行中状态)(Ready就绪,未得到时间片就绪状态)
Blocked 阻塞状态(如果遇到锁,线程就会变为阻塞状态等待另一个线程释放锁)
Waiting 等待状态(无限期等待)
Time_Waiting 超时等待状态(有明确结束时间的等待状态)
Terminated 终止状态(当线程结束完成之后就会变成此状态)
get是从服务器上获取数据,post是向服务器传送数据。
GET请求把参数包含在URL中,将请求信息放在URL后面,POST请求通过request body传递参数,将请求信息放置在报文体中。
get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。
get安全性非常低,get设计成传输数据,一般都在地址栏里面可以看到,post安全性较高,post传递数据比较隐私,所以在地址栏看不到, 如果没有加密,他们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。
GET请求能够被缓存,GET请求会保存在浏览器的浏览记录中,以GET请求的URL能够保存为浏览器书签,post请求不具有这些功能。
HTTP的底层是TCP/IP,GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。
GET产生一个TCP数据包,对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);POST产生两个TCP数据包,对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据),并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
常用状态码
B+树
主键索引
二级索引:
官方16个
被频繁更新的字段应该慎重建立索引。
虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
尽可能的考虑建立联合索引而不是单列索引。
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
注意避免冗余索引 。
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
考虑在字符串类型的字段上使用前缀索引代替普通索引。
前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。
explain
不能,抽象类就是用来继承的,加了final不能被子类继承
try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally
1.首先,不管try…catch是否有异常或者有return,只要有finally,都是要执行的
2.当try有return 语句,没有产生异常时,执行到return语句时,会先算出return 表达式的值,并将其保存起来。注意,此时没有返回,只是计算表达式的值并保存起来,然后再去执行finally代码块,如果finally代码块有return 语句,程序执行到return语句,程序会提前结束,然后返回值,不会去执行try中的return
例如 try 中 有return a+b ,执行到这里只是将a+b的值保存起来。然后再去执行finally ,finally有return a+c ,那么程序执行到return a+c时,便会返回a+c的结果,不会再去执行try中的return.
3.当try有异常,catch有return语句时,程序执行到try中有异常的地方,异常被捕获,跳转到catch代码块,执行到return语句时,同样只是保存return 表达式的值,然后再去执行finally代码块。
4、如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
5、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值