5 Star 1 Fork 0

zenka / java面试

Create your Gitee Account
Explore and code with more than 6 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Without author's permission, this code is only for learning and cannot be used for other purposes.
Clone or download
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README.md

一、redis

1.redis部署模式

  1. 单机模式:简单的单机版模式
  2. 主从模式:主节点写,副节点读,master死掉无法选master,搭建方式:只需要在配置文件中加上一行配置:slaveof 192.168.1.1 6379
  3. 哨兵模式:主节点写,副节点读,监控进程负责master死机选择slaveof为master
  4. 集群模式:整个集群的实例被分成了两部分:master和slaver。每个master仅负责自身所分配到的槽的读写请求,slaver仅负责读请求。同时集群会通过选举机制将失效的master对应的slaver提升为master,保证整个集群的高可用。 对比sentinel模式的提升:当集群容量不足时,可以动态的添加设备已进行性能提升。整个集群的最大容量不再受制于集群中内存最小的机器。 存在问题:当master失效时仍然可能存在丢失已经提交的指令的情况,进而出现缓存不一致。

2.redis5大数据结构类型

1.String

  • 使用场景:限流,秒杀,数值计算,二进制流计算,可以统计用户的活跃天数,登录次数等

2.list

  1. 特点:栈,数组,队列
  • 使用场景:帖子,评论

3.hash

  • 使用场景:用户信息,详情页,聚合的信息

4.set(无序集合)

  1. 特点:无序,去重

  2. 查询去重,只需要设置cunt的值为正数就能查询结果

     SRANDMEMBER kk 5
  3. 查询所有重复和不重复,只需要设置cunt的值为负数就能查询结果

     SRANDMEMBER kk -2
  • 随机事件,抽奖,集合操作,推荐系统,(取两个key值共同的集合就是一样的,也可以取两个共同没有的,列入推荐游戏,我在玩,好友没玩,就给好友推荐游戏,推荐可能认识的人,就是交集)

5.SortSet(有序集合)

  1. 去重,动态排序
  • 使用场景:排行榜,从小到大,动态分页(给出区间拿出来),添加进来造层缓存内存指针返回判断。

2.Redis存储

  1. 内存、热数据、key valu形式,没有全量IO,存储部分热数据,没有多表关系的概念

Hbase

  1. 数据库查询快:屏蔽全量IO,数据分治,Key,Value形式,内存

  2. 范式:数据存一张表的时候,有些数据会产生冗余,相同的部分,把它拆分成表查询的就不会产生冗余 ,垂直切割

  3. 热数据存内存,全量存磁盘,非关系数据

  4. HDFS分布式文件系统

  5. 缓存中间件:分布式存储消息转发,数据存储在磁盘,去数据先到进程到内核再到磁盘,中间内存内存映射到数据文件,直接读取,append追加,断文件存储,读取数据给出偏移量到内核零拷贝里面读取,

三、spring

  1. spring两个技术,ioc,aop

1. ioc

  • 加载xml配置文件,通过dom4j去解析文件,利用工厂模式和反射去创建对象,把对象交给spring管理,这就是ioc容器

2.aop

  • 面向切面编程,通过jdk或者cglib动态代理实现目标对象的接口方法

  • Spirng的AOP的动态代理实现机制有两种,分别是:

    1)JDK动态代理:

    具体实现原理:

    1、通过实现InvocationHandlet接口创建自己的调用处理器

    2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

    3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

    4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入

    JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,

    Spring通过java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

    2、CGLib动态代理:

    CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

    两者对比:

    JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。

    CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。

    使用注意:

    如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制)

    如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

    那么如何选择的使用代理机制了?

    通过配置Spring的中aop:config标签来显示的指定使用动态代理机制 proxy-target-class=true表示使用CGLib代理,如果为false就是默认使用JDK动态代理

  • Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。

  • Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。

  • Aspect:切面,即PointcutAdvice

  • Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。

  • Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

四、序列化

简要解释:
  序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
  序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,
然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

详细解释:

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

  只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包

1.概念

  序列化:把Java对象转换为字节序列的过程。   反序列化:把字节序列恢复为Java对象的过程。

2.用途

  对象的序列化主要有两种用途:   1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;   2) 在网络上传送对象的字节序列。

五、jvm虚拟机

  1. 栈:线程运行的地方,先进后出
  2. 堆:存对象数据,所有的变量
  3. 方法区:静态变量,常量,类的描述
  4. 程序计数器:计算运行到哪行代码乐,知道继续从哪里继续运行
  5. 类装载子系统:启动调用
  6. 字节码执行程序:修改程序计数器
  7. 本地方法栈:调用本地系统的一些方法
  • 本地方法栈、栈、程序计数器都需要内存
  • 每次GC都会对对象年龄+,加到15就会放到老年代
  • 内存调优的目的
  1. 减少gc的次数和时间,gc会导致线程暂停运行
  2. 优化新生代的内存,s0,和s1的内存,根据项目实际情况
  3. gc root作用检查堆里的对象变量层层查询是否有引用,没有引用就全部gc回收
  4. OOM内存溢出
  5. STW,stop the world(停止线程)
  6. 新建的对象如果很大直接放入老年代
  7. gc算法:
    1. 标记法:对没有做标记的全部回收(存在内存碎片化问题)
    2. 复制法:将活着的对象复制到另一块,自己清理这一块,解决碎片化(复制存活的对象到另一半内存,这一半直接清理掉)新生代就是这种算法
    3. 标记压缩法:复制和标记的结合版,没标记的直接清理,标记的放到一块,压缩花时间,消耗性能(老年代使用这种算法
    4. 保守式GC法:就是“不能识别指针和非指针的GC
    5. 准确式GC: 准确式GC能够正确识别指针和非指针的GC

六、线程之间通信的几种方式

  1. 内存共享
  2. 管道
  3. 信号量
  4. 消息队列
  5. socket

七、java中的几种流

八、Spring中自动装配的方式

  1. 手动装配Bean
  2. 根据类型自动装配Bean
  3. 根据名字自动装配Bean
  4. 构造函数(constructor)自动装配Bean
  5. 自动装配先看是否存在constructor方法,若没有使用byType的方式进行自动装配

九、Spring中Bean的作用域

  1. 单例模式:启动时创建所有对象,使用时创建对象、启动时不创建
  2. 原型模式:使用时克隆原对象的副本,使用后销毁

十、八大基本数据类型和String

1. 八大基本数据类型:

  • 字符型:char
  1. char属于java中的字符型,占2字节16bit,可以赋值单字符以及整型数值, 变量初始化无默认值,包装类Character。
  • 整型:byte, short, int, long
  1. byte属于Java中的整型,长度为1字节8bit,取值10000000(-128)到 01111111(127),变量初始化默认值为0,包装类Byte
  2. short属于Java中的整型,长度为2字节16bit,取值10000000 00000000(-32768)到 01111111 11111111(32767),变量初始化默认值为0,包装类Short
  3. int属于Java中的整型,长度为4字节32bit,取值-2^31 (-2,147,483,648)到 2^31-1(2,147,483,647),变量初始化默认值为0,包装类Integer
  4. long属于Java中的整型,长度为8字节64bit,取值-2^63 (-9,223,372,036,854,775,808‬)到 2^63-1(9,223,372,036,854,775,8087),变量初始化默认值为0或0L,包装类Long
  • 浮点型:double,float(Java中浮点型数据无法由二进制直接表示,而是一种对于实数的近似数据表示法,它遵循[IEEE 754](https://baike.baidu.com/item/IEEE 754/3869922)标准)
  1. float属于Java中的浮点型,也叫单精度浮点型,长度为4字节32bit,变量初始化默认值0.0f,包装类Float
  2. double属于Java中的浮点型,也叫双精度浮点型,长度为8字节64bit,变量初始化默认值0.0d,包装类Double
  • 布尔型:boolean(仅有两个值true, false,变量初始化默认值false)

2.String、StringBuffer与StringBuilder之间区别

  1. String使用char[]数组存储private final char value[];值不能够动态改变
  2. StringBuffer使用链表方式存储,synchronized同步锁,线程安全,效率低,能够修改值
  3. StringBuilder使用链表方式存储,无锁,线程不安全,效率高,能够修改值

小结

(1)如果要操作少量的数据用 String;

(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;

(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder

十一. list,map,set区别

  1. List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
  2. Set下有HashSet,LinkedHashSet,TreeSet
  3. List下有ArrayList,Vector,LinkedList
  4. Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
  5. Collection接口下还有个Queue接口,有PriorityQueue类
  • 总结
  1. list重复有序
  • ArrayList 优点: 底层数据结构是数组,查询快,增删慢。 缺点: 线程不安全,效率高
  • Vector 优点: 底层数据结构是数组,查询快,增删慢。 缺点: 线程安全,效率低
  • LinkedList 优点: 底层数据结构是链表,查询慢,增删快。 缺点: 线程不安全,效率高
  1. set无序唯一
  • HashSet 底层数据结构是哈希表。(无序,唯一) 如何来保证元素唯一性? 1.依赖两个方法:hashCode()和equals()
  • LinkedHashSet 底层数据结构是链表和哈希表。(FIFO插入有序,唯一) 1.由链表保证元素有序 2.由哈希表保证元素唯一
  • TreeSet 底层数据结构是红黑树。(唯一,有序) \1. 如何保证元素排序的呢? 自然排序 比较器排序 2.如何保证元素唯一性的呢? 根据比较的返回值是否是0来决定
  1. Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
    • TreeMap是有序的,HashMap和HashTable是无序的。
    • Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。
    • 这就意味着:
      • Hashtable是线程安全的,HashMap不是线程安全的。
      • HashMap效率较高,Hashtable效率较低。 如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
      • Hashtable不允许null值,HashMap允许null值(key和value都允许)
      • 父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
    private String a = "1";

    private StringBuffer b = new StringBuffer();
    private StringBuilder c = new StringBuilder("54154");

    private Map<String,Object> map = new HashMap<>();//无序,可以存储null值
    private Map<String,Object> map6 = new ConcurrentHashMap<>();//无序,可以存储null值,有锁能同步
    private Map<String,Object> map2 = new LinkedHashMap<>();//链表有序,
    private Map<String,Object> map4 = new Hashtable<>();//不能存null值,线程安全同步
    private Map<String,Object> map5 = new TreeMap<>();//树有序

    private List<String> list = new ArrayList<>();//数组结构,查询块,增删慢private static final Object[] EMPTY_ELEMENTDATA = {};
    private List<String> list1 = new Vector<>();//数组结构,查询慢,责备增删快,有synchronized锁
    private List<String> list2 = new LinkedList<>();//链表结构,查询慢,增删快。

    private Set<String> set1 = new HashSet<>();//hash算法无序,
    private Set<String> set2 = new TreeSet<>();//红黑树有序升序
    private Set<String> set3 = new LinkedHashSet<>();//实际数据查询速度而定

十二、session和cookie和Jwttoken

  1. session存在服务器数据,重要数据存放

  2. cookie存在客户端数据,次要数据存放,不能超过4k

  3. jwt

    1. header:声明类型和加密算法(HMAC SHA256等)
    2. playload:载荷就是存放有效信息的地方。我们自己定义一些用户信息
    3. signature:一个签证信息,这个签证信息由三部分组成
      1. header (base64后的)
      2. payload (base64后的)
      3. secret
  4. 总结

    优点

    • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
    • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
    • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
    • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

    安全相关

    • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
    • 保护好secret私钥,该私钥非常重要。
    • 如果可以,请使用https协议

十三、SpringMVC

  1. M(model)模型 javaBean

  2. V(view)视图 html

  3. C(controller)控制器 servlet

  4. 流程

    (1)用户发送请求至前端控制器DispatcherServlet; (2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle; (3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet; (4)DispatcherServlet 调用 HandlerAdapter处理器适配器; (5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器); (6)Handler执行完成返回ModelAndView; (7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet; (8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析; (9)ViewResolver解析后返回具体View; (10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中) (11)DispatcherServlet响应用户。

  5. 优点

    (1)可以支持各种视图技术,而不仅仅局限于JSP;

    (2)与Spring框架集成(如IoC容器、AOP等);

    (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。

    (4) 支持各种请求资源的映射策略。

十四、数据库

  1. 关系型数据库:mysql,Oracle,sqlServer
  2. 非关系型数据库:redis,hadoop
  3. 关系型数据库三大范式
    1. 同一列不能有多个属性值,如name列存了名字又存了性别
    2. 每行可以唯一区分,每行都有唯一标识主健,标识这条数据是唯一的
    3. 其他表中出现了和本表相同的内容,就是外键,不允许出现相同
  4. 事务:
    1. 原子性(要么一起成功,要么一起失败)
    2. 一致性(中途失败了要对前面的操作进行回滚)
    3. 隔离性(不能有其他事务的干扰)
    4. 持久性(开始了,就不能停止)

1.mysql

  1. MySQL默认连接数,到安装目录里my.ini查看修改,默认100

  2. 分页:数据一段显示不完,分段显示

    1. limit关键字分页
  3. 存储过程:创建时编译,以后一直使用,创建一次

  4. 数据库优化过程方式

    1. 定位:查找,慢查询,并优化(项目启动时,开启慢查询打印日志,过一段时间查看慢查询日志)
      • explain语句可以不执行sql语句实现分析sql语句,并查看用到的索引
    2. 创建索引(创建合适的索引,根据索引查询到对应的记录)
    3. 读表(当一张表的字段比较多时,采用水平分割或者垂直分割方式来优化 )
      1. 垂直分割
        1. 时间分表,时效性
        2. id自增分表,id自增性
        3. hash分表,hash算法得出表名进行查询
      2. 水平分表
        1. 1行到100w行
    4. 读写分离(一台服务器不满足需求时,多台服务器做读写分离集群)
      1. 主从同步:主来写,其他来同步
      2. 负载均衡实现写的到主,读的去其他数据库
    5. redis缓存(高并发大流量数据访问用redis进行缓存)
  5. 存储引擎:

    1. MyISAM,对事务的要求不高(不支持事务,没有同步锁,不支持外键,速度快),已查询和添加为主,场景:发帖,查询数据
    2. InnoDB,对事务的要求高,场景:重要信息,账单,账号
    3. MEMORY:数据变化频繁,不需入库,要频繁查询修改操作,速度快
  6. 索引:

  • 1.普通索引 2.唯一索引 3.主键索引 4.组合索引 5.全文索引
  1. 弊端:占用磁盘空间,对增删改有影响,变慢

  2. 场景

    1. 必须是为了查询而创建的,where条件
    2. 字段内容不是频繁变化的
    3. like查询%左边不会用到索引
    4. or单独使用时才能使用索引
    5. 字符串必须使用""括起来
    6. 多列复合索引第一部分使用,第二部分就不使用
  3. 索引结构:

    ![](/Users/haiqiu/Library/Application Support/typora-user-images/image-20201118233523152.png)

  4. sql语句优化:

    1. 插入时关闭索引,插入完成恢复索引
    2. 关闭唯一校验
    3. 多条插入语句合并成一条插入
    4. order By 多使用索引排序
    5. limit外链接查询
  5. 数据库锁:

    1. 乐观锁、悲观锁

      • 乐观锁(Optimistic Lock):假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 乐观锁不能解决脏读的问题。

      乐观锁, 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

      • 悲观锁(Pessimistic Lock):假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

      悲观锁,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

2.分布式锁

应用场景:解决数据一致性的问题,防止并发过多导致数据出现偏差

  1. 数据库实现排他锁

    缺点

    1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。 2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。 3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。 4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

    解决方案: 1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。 2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。 3、非阻塞的?搞一个while循环,直到insert成功再返回成功。 4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

  2. redis实现

缺点:

在这种场景(主从结构)中存在明显的竞态: 客户端A从master获取到锁, 在master将锁同步到slave之前,master宕掉了。 slave节点被晋级为master节点, 客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。安全失效

  1. zookeeper实现

缺点:

 性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。



 其实,使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。(所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。)

三种方案的比较

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

从理解的难易程度角度(从低到高)

数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

Zookeeper > 缓存 > 数据库

十五、多线程高并发

  1. 进程包含多个线程

  2. 线程实现方式:实现Runnable接口或者继承Thread类或者实现Callable接口(Callable实现需要实现new FutureTask调用)

  3. //线程实现Callable接口的多线程方法,带返回参数泛型
    public class TestCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "132456";
        }
    
        public static void main(String[] args) {
            
            TestCallable testCallable = new TestCallable();
            
            FutureTask<String> stringFutureTask = new FutureTask<>(testCallable);
            
            new Thread(stringFutureTask).start();
            
            try {
                System.out.println(stringFutureTask.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
  4. 从定义中可以发现,在 Thread 类中的 run() 方法调用的是 Runnable 接口中的 run() 方法,也就是说此方法是由 Runnable 子类完成的,所以如果要通过继承 Thread 类实现多线程,则必须覆写 run()。

    实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的,如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。

  5. 线程状态变化:

    1. 要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。下面分别介绍一下这几种状态:

      • 创建状态 new

      在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。

      • 就绪状态 Runnable

      新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。

      • 运行状态 run

      当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。

      • 阻塞状态 Blocked

      一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。

      • 死亡状态 Dead

      线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

      在此提出一个问题,Java 程序每次运行至少启动几个线程?

      回答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。

    2. 三种方式的优缺点 

      • 采用继承Thread类方式:

         (1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。    (2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

      • 采用实现Runnable接口方式:

         (1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。    (2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

      • Runnable和Callable的区别:

         (1)Callable规定的方法是call(),Runnable规定的方法是run().    (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得    (3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常    (4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

      start()和run()的区别

      • start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行
      • run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)
  6. 同步以及死锁

    1. 锁机制:

      1. 偏向锁(无锁状态)
      2. 轻量级锁(线程拿锁)
      3. 重量级锁()
      4. 自旋锁(循环拿锁)
    2. 一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。

      同步方法

      除了可以将需要的代码设置成同步代码块外,也可以使用 synchronized 关键字将一个方法声明为同步方法。

      死锁

      同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如,现在张三想要李四的画,李四想要张三的书,张三对李四说“把你的画给我,我就给你书”,李四也对张三说“把你的书给我,我就给你画”两个人互相等对方先行动,就这么干等没有结果,这实际上就是死锁的概念。

      所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。

      如何避免死锁? 在有些情况下死锁是可以避免的。三种用于避免死锁的技术:

      1. 加锁顺序(线程按顺序加锁)
      2. 加锁时限(线程尝试获取锁的时候加上时间,超过时间则放弃对该锁的请求,并释放自己占有的锁)
      3. 死锁检测
  7. CAS互相交换:乐观锁的一种方式,不加锁synchronized,更新值查看有没有被人改过,没有就更新,有就读取值重新更新

  8. Volatitle开启线程之间的可见性

  9. mesi协议和锁总线

十六、spring-cloud

  1. 注册中心Eureka

    1. 云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
  2. 服务网关

    1. Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。

    2. SpringCloud Gateway(和zool区别:路由、断言、过滤器)

      (1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0

      (2)集成 Hystrix 断路器

      (3)集成 Spring Cloud DiscoveryClient

      (4)Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters

      (5)具备一些网关的高级功能:动态路由、限流、路径重写

  3. 服务调用Open Feign

    1. Hystrix(服务熔断器)

    熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。

    1. Ribbon(负载均衡)

      提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。

  4. 配置中心Spring Cloud Config

    1. Spring

    配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。

  5. 消息总线Bus

    1. Spring

      事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。

十七、学习目录

第一次当课代表[奋斗][奋斗] ## 编程基础(掌握) ### JAVA语法 #### Java基础

#### JVM - 类加载机制 - 字节码执行机制 - JVM内存模型 - GC垃圾回收 - JVM性能监控与故障定位 - JVM调优

#### 多线程 - 并发编程的基础 - 线程池 - 锁 - 并发容器 - 原子类 - JUC并发工具类### 数据结构和算法 #### 数据结构 - 字符串 - 数组 - 链表 - 堆、栈、队列 - 二叉树 - 哈希 - 图

#### 算法 - 排序 - 查找 - 贪心 - 分治 - 动态规划 - 回溯### 计算机网络 - ARP协议 - IP、ICMP协议 - TCP、UDP协议 - DNS、HTTP/HTTPS协议 - Session/Cookie### MySQL数据库 - SQL语句的书写 - SQL语句的优化 - 事务、隔离级别 - 索引 - 锁### 操作系统 - 进程、线程 - 并发、锁 - 内存管理和调度 - I/O原理### 设计模式 - 单例 - 工厂 - 代理 - 策略 - 模板方法 - 观察者 - 适配器 - 责任链 - 建造者

——————————————————————————

### 前端(了解) - 基础套餐(大致了解,2-3天) - 三大件 - HTML - JavaScript - CSS - 基础库 - jQuery - Ajax - 模板框架 - JSP/JSTL(已过时) - Thymeleaf - FreeMarker - 组件化框架 - Vue - React - Angular-----------------------------------------------

## 运维知识(配置) - Web服务器 - Nginx - 应用服务器 - Tomcat - Jetty - Undertow - CDN加速 - 持续集成/持续部署 - Jenkins - 代码质量检查 - sonar - 日志收集和分析 - ELK

-----------------------------------

## 成神之路 - 徒手撕源码 - 光脚造轮子 - 闭着眼睛深度调优 - 吊打面试官

-----------------------------------------------

## 研发工具 ### 集成开发环境 - Eclipse - Intellij IDEA - VSCode

### Linux系统(了解) - 常用命令 - Shell脚本### 项目管理/构建工具(掌握) - Maven - Gradle### 代码管理工具(了解) - SVN - Git

——————————————————

## 应用框架 - 数据库框架 - ORM层框架(掌握) - Mybatis - Hibernate - JPA - 连接池(掌握) - Druid - HikariCP - C3P0 - 分库分表 - MyCAT - Sharding-JDBC - Sharding-Sphere - 搜索引擎(了解) - ElasticSearch - Solr - 分布式/微服务(了解,2-3week) - 服务发现/注册 - Eureka - Consul - Zookeeper(重要) - Nacos - 网关 - Zuul - Gateway - 服务调用(负载均衡) - Ribbon - Feign - 熔断/降级 - Hystrix - 配置中心 - Config - Apollo - Nacos - 认证和鉴权(稍微重要些) - Spring Security - OAuth2 - SSO单点登录 - 分布式事务 - JTA接口——Atomikos组件 - 2PC、3PC - XA模式 - TCC模式——tcc-transaction、ByteTCC、EasyTransaction、SeaTa - SAGA模式——ServiceComb、Seata - LCN模式——tx-Icn - 任务调度 - Quartz - Elastic-Job - 链路追踪和监控 - Zipkin - Sleuth - Skywalking - 日志分析与监控——ELK - ElasticSearch - Logstash - Kibana - 虚拟化/容器化 - 容器化——Docker - 容器编排技术——Kubernetes、Swarm

十八、Object对象内存

  1. markword:对象头,new对象占用16个字节,记录锁信息、GC年龄信息,hash code信息

十九、重写和重载

重写(Overriding) 重载(Overloading)
类的数量 父子类、接口与实现类 本类
方法名称 一致 一致
参数列表 一定不能修改 必须修改
返回类型 一定不能修改 可以修改
异常 可以减少或删除,但不能扩展 可以修改

二十、spring-cloud

  1. 注册中心Eureka

    1. 云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
  2. 服务网关

    1. Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。

    2. SpringCloud Gateway(和zool区别:路由、断言、过滤器)

      (1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0

      (2)集成 Hystrix 断路器

      (3)集成 Spring Cloud DiscoveryClient

      (4)Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters

      (5)具备一些网关的高级功能:动态路由、限流、路径重写

  3. 服务调用Open Feign

    1. Hystrix(服务熔断器)

      熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。

    2. Ribbon(负载均衡)

      提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。

  4. 配置中心Spring Cloud Config

    1. Spring

      配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。

  5. 消息总线Bus

    1. Spring

      事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。

二十一、spring-boot原理

1.自动装载 自动装载,即将 Bean 装入 IOC。 首先 springboot 通过 maven 的方式,继承于父工程来获得所有 jar。但是 jar 太多,不可能都装入,因此底层通过 classloader+注解的方式,将满足条件的必要的 jar 装入 IOC。 2.自动配置 自动配置,既然要装入 IOC,那么就要完成对象的初始化,即自动配置,而自动配置也是通过注解+class 的方式完成。将配置写在一个标记有特定注解的类中,然后通过注解找到该类,完成对对象的初始化。

简单的说 springboot 的自动装载机制就是通过配置@EnableAutoConfiguration 将配置为@Configuration 下的@Bean 方法加载到 spring 容器中,这个过程就是 spring 自动装载机制。首先 springboot 自动装配功能是为了满足啥?是为了满足其他的插件进行扩展,因为有很多外部的 bean 我们没法管理,也不知道具体包的路径,这个时候 springboot 提供了自动装配功能,让我们外部的类能够注入到 spring 项目中。第二,如果说这个是 springboot 的自动装配功能不如说是 spring 的自动装配功能。因为 springboot 使用了 spring3.1 出来的 ImportSelector 动态 bean 的装载实现的自动装载机制,同时使用了 META-INF/spring.factories 中的 SPI 机制实现了 spring 自动扫描到自动装载的 bean 的机制。后面再说几点,spring 发展是由 XML 文件到注解方式的一个循序渐进的过程,比如@Component 以及它的派生注解@Controller 等,最后 spring 直接把 XML 文件变成了@Configuration 注解,这样理解就可以理解成 springboot 自动装载机制是把外部的 xml 文件的 Bean 配置导入到了自己的项目中,让 Bean 在自己的项目中运行。而起到关键作用的@EnableAutoConfiguration 只是作为了一个中介者的作用。

二十二、线程池

E1、线程池的优势

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗; (2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行; (3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。 (4)提供更强大的功能,延时定时线程池。

2、线程池的主要参数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

5、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

3、线程池流程

img

线程池流程

1、判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则。 2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。 3、判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。

(1、判断线程池中当前线程数是否大于核心线程数,如果小于,在创建一个新的线程来执行任务,如果大于则 2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。 3、判断线程池中当前线程数是否大于最大线程数,如果小于,则创建一个新的线程来执行任务,如果大于,则执行饱和策略。)

4、线程池为什么需要使用(阻塞)队列?

回到了非线程池缺点中的第3点: 1、因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。

另外回到了非线程池缺点中的第1点: 2、创建线程池的消耗较高。 或者下面这个网上并不高明的回答: 2、线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。

5、线程池为什么要使用阻塞队列而不使用非阻塞队列?

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。 当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。 使得在线程不至于一直占用cpu资源。

(线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下 while (task != null || (task = getTask()) != null) {})。

不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢?

6、如何配置线程池

CPU密集型任务 尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

IO密集型任务 可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

混合型任务 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。 因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。 因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

7、java中提供的线程池

Executors类提供了4种不同的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor

img

java线程池对比

1、newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)

2、newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)

3、newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。

4、newScheduledThreadPool:适用于执行延时或者周期性任务。

8、execute()和submit()方法

1、execute(),执行一个任务,没有返回值。 2、submit(),提交一个线程任务,有返回值。 submit(Callable task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。

submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。 submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。

Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。

https://www.cnblogs.com/dolphin0520/p/3949310.html

9、阻塞队列

Comments ( 2 )

Sign in for post a comment

About

Cancel

Releases

No release

Contributors

All

Activities

load more
can not load any more
1
https://gitee.com/zenka/java-interview.git
git@gitee.com:zenka/java-interview.git
zenka
java-interview
java面试
master

Search