每天小问题 ——day01
作者:互联网
一.Spring bean 是线程安全的吗
线程不安全的,spring容器本身并没有针对于bean采取什么线程安全策略
Spring 的 bean 作用域(scope)类型
1)、singleton:单例,默认作用域。
2)、prototype:原型,每次创建一个新对象。
3)、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
4)、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
5)、global-session:全局会话,所有会话共享一个实例。
原型模式每次都创建一个新对象,不涉及线程安全问题
单例模式所有线程都共享一个单例实例Bean,因此是存在资源的竞争,无状态bean线程只查询不操作就是安全的
单例懒汉式线程不安全,需进行同步操作
bean有状态就是有数据存储功能
bean无状态就是不会保存数据
Spring mvc 的 Controller、Service、Dao等都是无状态的,只针对于方法本身
如果涉及线程安全添加@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
二.threadLocal
场景:每个线程需要有自己单独的实例
实例需要在多个方法中共享,但不希望被多线程共享
例如:Session 、SimpleDateFormat
原理:首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。
问题:
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value
ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,
并且之后也不再调用 get()、set()、remove() 方法的情况下。
建议回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。 尽量在代理中使用try-finally块进行回收:myThreadLocal.remove();
安全随机数:ThreadLocalRandom.current().nextInt(100)
三.分库分表
单表的数据量达到1000W
两种方式:垂直(纵向)切分和水平(横向)切分
1)垂直(纵向)切分
a)垂直分库
就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库
b)垂直分表
垂直分表是基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题,
MySQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。
垂直切分的优点:
解决业务系统层面的耦合,业务清晰
与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈
缺点:
部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度
分布式事务处理复杂
依然存在单表数据量过大的问题(需要水平切分)
2)、水平(横向)切分
当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
水平切分分为库内分表和分库分表,是根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果
库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻MySQL数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。
水平切分的优点:
不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
应用端改造较小,不需要拆分业务模块
缺点:
跨分片的事务一致性难以保证
跨库的join关联查询性能较差
数据多次扩展难度和维护量极大
水平切分后同一张表会出现在多个数据库/表中,每个库/表的内容不同。几种典型的数据分片规则为:
1、根据数值范围
按照时间区间或ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将userId为19999的记录分到第一个库,1000020000的分到第二个库,以此类推。某种意义上,某些系统中使用的"冷热数据分离",将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。
这样的优点在于:
单表大小可控
天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移
使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。
缺点:
热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
2、根据数值取模
一般采用hash取模mod的切分方式,例如:将 Customer 表根据 cusno 字段切分到4个库中,余数为0的放到第一个库,余数为1的放到第二个库,以此类推。这样同一个用户的数据会分散到同一个库中,如果查询条件带有cusno字段,则可明确定位到相应库去查询。
优点:
数据分片相对比较均匀,不容易出现热点和并发访问的瓶颈
缺点:
后期分片集群扩容时,需要迁移旧的数据(使用一致性hash算法能较好的避免这个问题)
容易面临跨分片查询的复杂问题。比如上例中,如果频繁用到的查询条件中不带cusno时,将会导致无法定位数据库,从而需要同时向4个库发起查询,再在内存中合并数据,取最小集返回给应用,分库反而成为拖累。
分库分表面临的问题:
a)当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用"XA协议"和"两阶段提交"处理。
事务补偿还要结合业务系统来考虑
b)跨节点关联查询 join 问题
c)跨节点分页、排序、函数问题 如:order by, limit、sum、max等
d)全局主键避重问题 uuid 32位16进制 或者维护主键表
四.volatile
应用于并发编程
可见性:在多线程环境下,某个共享变量如果被其中一个线程给修改了,其他线程能够立即知道这个共享变量已经被修改了,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的工作空间中读取。
原理:当一个变量被声明为volatile时,在编译成会变指令的时候,会多出下面一行:
0x00bbacde: lock add1 $0x0,(%esp);
这句指令的意思就是在寄存器执行一个加0的空操作。不过这条指令的前面有一个lock(锁)前缀。
当处理器在处理拥有lock前缀的指令时:
在之前的处理中,lock会导致传输数据的总线被锁定,其他处理器都不能访问总线,从而保证处理lock指令的处理器能够独享操作数据所在的内存区域,而不会被其他处理所干扰。
但由于总线被锁住,其他处理器都会被堵住,从而影响了多处理器的执行效率。为了解决这个问题,在后来的处理器中,处理器遇到lock指令时不会再锁住总线,而是会检查数据所在的内存区域,如果该数据是在处理器的内部缓存中,则会锁定此缓存区域,
处理完后把缓存写回到主存中,并且会利用缓存一致性协议来保证其他处理器中的缓存数据的一致性。
有序性:
volatile能保证线程安全吗?
不一定,
运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
变量不需要与其他状态变量共同参与不变约束。
五、String str = “i” 与 String str = new String(“i”) 一样吗?
intern()方法在元数据空间中常量池找i 返回引用,没有,则生成
在堆中返回String 的引用
六、
String + 不断遍历生成StringBuild对象,append()
与StringBuild,遍历append()差距很大
七.重载为什么不能根据返回类型作为区分?
在调用时不需要返回值。
void f();
int f();
调用 f() 没办法区分
参考java编程思想
八.java 创建对象方式
1.new
2.Class.forName.newInstance();
3.clone()浅克隆 clone引用 堆中地址不同
4.反序列化,比如调用 ObjectInputStream 类的 readObject() 方法。 深克隆
九.abstract Class 与interface
相同点:声明抽象接口,
抽象类可以有具体方法,可以有静态方法 1.8以后接口也支持 default 修饰具体方法
不同点: extends,implements
十.
short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1;没有错
java 编译器对+=优化
十一.Integer 和 int 的区别?
1)Integer java int封装类,int java八大类型之一
2)默认null,默认0
3)必须实例化,不必实例化
Integer与int比较 自动拆箱 true
Integer为封装类型,比较自动拆箱,valueOf()从缓存获取-128-127
非 new 生成的 Integer 变量和 new Integer() 生成的变量进行比较时,结果为 false。因为非 new 生成的 Integer 变量指向的是 Java 常量池中的对象,而 new Integer() 生成的变量指向堆中新建的对象,两者在内存中的地址不同;
十二.
final、finally、finalize 的区别
final:用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、被其修饰的类不可被继承
finally:用于try catch ...是否有异常,都会走的代码块
finallize:object中的方法,在垃圾回收时提供调用
标签:每天,day01,问题,切分,ThreadLocal,线程,内存,分片,数据 来源: https://www.cnblogs.com/ococo/p/15956551.html