一、Oauth2 简单讲一下?
Oauth2 有四种客户端授权模式:
- implicit:简化模式,(不安全,适用于纯静态页面应用)
- authorization code:授权码模式(功能最完整、流程最严密的授权模式,通常使用在公网的开放平台
- resource owner password credentials:密码模式(一般在内部系统中使用,调用者是以用户为单位。)
- client credentials:客户端模式(一般在内部系统之间的API调用。两个平台之间调用。调用者是以平台为单位。)
授权码模式
适用于有自己的服务器的应用,它是一个一次性的临时凭证,用来换取
access_token
和refresh_token
。一旦换取成功,code
立即作废,不能再使用第二次
- 用户请求我们业务网站,选择第三方登录,以qq为例,用户点击qq登录,此时调用我们后台接口,我们根据参数请求QQ授权服务器
clientId
、clientSecret
、redirectUri
拿到登录授权地址后,重定向到QQ的登录授权页面 - 用户登录QQ,选择要授权的权限,例如QQ号,头像,地区等
- QQ授权服务器带着授权码
code
回调我们第一步给他的回调地址 - 我们在回调地址接口中,拿着
code
、clientId
、clientSecret
到 QQ授权服务器 换取 token,换取后 code 失效 - 我们拿着刚刚换取的
token
去QQ资源服务器 获取 资源,可能是qq_id、昵称、邮箱、头像等等 - 拿到用户信息后,判断系统中是否已存在 qq_id,如果不存在,插入一条,如果存在,直接返回token,登录结束
二、事务的特性?
-
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚
-
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态, 假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000
-
隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
-
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
事务可能导致的问题
- 脏读
- 不可重复读
- 幻读
数据库的隔离级别
1 未提交读
会发生脏读、不可重复读、幻读
2 已提交读 sqlserver、oracle
解决脏读、会发生不可重复读、幻读
3 可重复读 mysql默认隔离级别
解决脏读、不可重复读,会发生幻读
4 可串行化
所有都可避免,性能很低
三、什么时候适合建立索引,什么时候不适合?
适合创建索引条件
1、主键自动建立唯一索引
2、频繁作为查询条件的字段应该建立索引
3、查询中与其他表关联的字段,外键关系建立索引
4、查询中排序的字段,排序字段若通过索引去访问将大大提高排序效率
5、查询中统计或者分组字段
不适合创建索引条件
1、表记录少的
2、经常增删改的表
3、频繁更新的字段不适合创建索引,因为每次更新不单单是更新记录,还会更新索引,保存索引文件
4、数据重复且分布均匀的字段。例如性别字段,只有男女,不适合建立索引。
四、说一说锁升级的过程?
锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁(自旋锁)、重量级锁,锁状态只能升级,不能降级
-
无锁
无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
无锁的特点是修改操作会在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。
-
偏向锁
初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程ID也在对象头里),如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
-
轻量级锁(自旋锁)
轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
一旦有第二个线程加入锁竞争,偏向锁就升级为轻量级锁(自旋锁)。这里要明确一下什么是锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。
在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。获取锁的操作,其实就是通过CAS修改对象头里的锁标志位。先比较当前锁标志位是否为“释放”,如果是则将其设置为“锁定”,比较并设置是原子性发生的。这就算抢到锁了,然后线程将当前锁的持有者信息修改为自己。
长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果多个线程用一个锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么synchronized就用轻量级锁,允许短时间的忙等现象。这是一种折衷的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销。
-
重量级锁
重量级锁显然,此忙等是有限度的(有个计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是CAS修改锁标志位,但不修改持有锁的线程ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。
重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源
五、软件工程的主流程?
1、问题定义
2、可行性研究
3、需求分析
4、总体设计
5、详细设计
6、编码与单元测试
7、综合测试
8、软件维护
六、java中的值传递
Java 语言的方法调用只支持参数的值传递
当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
例子1:
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
a = 20
b = 10
num1 = 10
num2 = 20
例子2:
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
1
0
例子3:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
//如果设置 x = 李四,则输出李四,说明修改了引用
//x.setName("李四");
}
}
x:小李
y:小张
s1:小张
s2:小李
总结:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
七、什么是字符串常量池?
字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用
八、Integer a= 127 与 Integer b = 127相等吗
对于对象引用类型:==比较的是对象的内存地址。 对于基本数据类型:==比较的是值。
如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
}
九、说一下 JVM 运行时数据区
线程共享:方法区、java堆。
线程私有:虚拟机栈、本地方法栈、程序计数器
- 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
- Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
- 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
- Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
- 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
十、Java 中都有哪些引用类型?
- 强引用:发生 gc 的时候不会被回收。
- 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
- 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
- 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。