java面试题

java并发面试问题:

1.聊聊你对CAS的理解:

并发包下的autoInteger的底层实现,当autointeger调用increment方法的时候,多个线程去调用这个方法,首先查询获取当前变量的值,对变量值进行修改,修改后继续查询当前值是否为以前的值,如果是的话做加一操作,如果不是前面的值,就获取最新的值继续上面这个操作,直到修改成功。


image.png

2.concurrentHashMap的实现原理解析:
java.1.7 把大数组拆分成多个小数组,每个数组对应一把锁。
java 1.8 对锁的粒度更加细化,存储还是一个大数组,如果两个线程对同一个位置进行put操作的时候,同一时间内只有一个线程能执行cas操作,就是刚开始获取一下数组[5]这个位置的值,然后执行CAS,线程1先去比较一下,如果没有变化,进行修改,同一时间其他线程执行CAS都会失败,如果其他人失败了,就需要在这个位置基于红黑树+链表的模式,synchronized(数组[5]),加锁,基于链表 或者红黑树在这个位置插进去自己的数据。

3.AQS的理解:

image.png

线程一和线程二都进行CAS获取锁,当线程1获取锁后,线程二进入一个队列中进行等待,如果为非公平锁,当线程3进行CAS的时候,刚才锁释放,线程三就会获取到锁,如果是公平锁的话,首先判断队列中有没有线程等待,如果有的话,先执行线程二,
4.线程池的原理:


image.png

线程池原理,首先有了任务当线程池没有满的时候,直接在线程池中执行,当线程池满了的时候就进入到阻塞队列中,线程池有空余线程了就会从阻塞队列中获取任务并且执行

5.线程池参数理解:

corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:60s 非核心线程数的空闲时间
new ArrayBlockingQueue(200)


线程图

实现功能图: 当有任务进来的时候首先创建核心线程数,当核心线程数都满了的情况,下来的任务进入阻塞队列中,当阻塞队列也满了的话,就查看是否设置了最大线程数是否大于核心线程数,如果设置了就开始创建非核心线程数,非核心线程就会去执行多余的任务当任务执行完了的话就会从队列中获取任务执行,当任务都执行完了的话,查看空闲时间,空闲时间到了非核心线程就会消亡。
如果非核心线程数数量也满了,就要传入RejectedExecutionHandler.选择拒绝策略。

6.[并发]java内存模型的理解:

image.png
package container;

import java.util.*;

public class TestHashMap {
    private static int i=0;
    public static void main(String[] args) {
        Map map=new HashMap();
        new Thread(new Runnable(){
            @Override
            public void run() {
                i++;
            }
        }).start();
        new Thread(new Runnable(){
            @Override
            public void run() {
                i++;
            }
        }).start();
    }
}

两个线程去修改当前的的值,类中有主内存,每个线程有自己的一片工作内存,首先一个线程使用read读取数值,使用load加载到自己的工作内存,使用use进行操作,使用assign返回给工作内存,把工作内存中的一个值传递给主内存,write写入主内存,线程二也是相同的操作,所以会出现并发问题。这就是并发线程模型。

7.syncronized和reentrantlock的区别

syncronized可以锁代码块、方法和对象
syncronized隐式的获得释放锁,ReentrantLock显示的获得锁,或者释放锁,
reentrantlock可以实现公平锁,
syncronized是同步阻塞,使用悲观并发的策略,Reentranklock使用同步非阻塞,采用的是乐观并发策略。
syncronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生,而reentranklock在发生异常时,如果没有主动通知unLock()去释放锁,就会造成死锁现象。因此需要在finally块中释放锁。
ReentrankLock是API级别的,syncronized是JVM级别。
ReentrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁,
syncronized在1.6之前是重量级锁,在1.6之后对syncronized进行了进一步的优化,
Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,代码块同步使用的是monitorenter和 monitorexit 指令实现的,而方法同步是通过Acc_syncronized后面的标识来确定该方法是否为同步方法。

  • 1.6之后syncronized引入了锁升级,引入了偏向锁,和轻量级锁,锁的状态变为了四种,
    -锁膨胀指锁从无锁->偏向锁->轻量级锁->重量级锁的单项升级
  • 消除指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的
    -锁粗化是指,将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁
public String method() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
// 伪代码:加锁操作
sb.append( + i);
// 伪代码:解锁操作
}
return sb.toString();
}

-自旋锁是指通过自身循环,尝试获取锁的一种方式,伪代码实现如下:
自旋锁优点在于它避免一些线程的挂起和恢复操作,因为挂起线程和恢复线程都需要从用户态转入内核态,这个过程是比较慢的,所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。

8.java内存模型中的原子性,有序性,可见性

  • 一次操作或者多次操作,要么所有的操作全部都得到有效的执行并且不会受到任何因素的干扰而中断,要么都不执行。
  • 当一个线程对共享变量进行了修改,其他的线程都是立即可以看到修改后的最新值。
    volatile(易变关键字)
    它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主内存(指示 JVM,这个变量是共享且不稳定,每次使用它都到主存中进行读取)
    -有序性 由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。
      static int num = 0;
    static int r1 = 0;

     static boolean ready = false;
    public static void main(String[] args) {
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("r1执行1111");
                if(ready) {
                    r1 = num + num;
                    System.out.println("r1进入8888");
                    System.out.println("r1true");
                } else {
                    System.out.println("r1false");
                    r1 = 1;
                }            }
        }).start();
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("r2先执行");
                num = 2;
                ready = true;            }
        }).start();
    }
  • volatile 加上volatile字段可以阻止指令重排。

mysql面试问题

1.myIsam和innodb的区别

  • myIsam不支持事务,不支持外键约束,索引文件和数据文件分开,对查询效果会很好,支持表锁,
  • innodb 1.支持行锁,2.支持外键,3.支持事物,4.读的效率低于myisam 5.写的效率高于myisam,6.支持自增AUTO_INCREMENT属性。
  1. mysql的索引实现原理:

从B树来分析:B是一种多路平衡树搜索树,比平衡二叉树存储更多的结点,相对于平衡二叉树每个结点存储更多结点,子节点最大的个数为B树的阶数,对比平衡二叉树高度也更低,结点上存储着键和值。
B+数是对B-数的更好的优化:B+数非叶子节点不存储数据,只有叶子节点存储数据,并且相邻的叶子节点之间连接起来,方便按照范围查找。
b+树的高度一般在2-4层,而每次IO能查100次,所以查询到数据为0.02到0.04秒
myIsam的索引文件和数据文件是分开的,B+数最后存储的是一个key键和物理地址,通过物理地区去数据文件中获取数据。
innodb要求必须要有主键(即使不创建主键系统也会默认创建一个主键),默认使用主键建立索引,他的索引文件也是数据文件。

3.聚簇索引和非聚簇索引

innodb中根据主键建立的索引也叫做聚簇索引,非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,
想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表

4.索引的使用规则

-全列匹配
有三个列,刚好有三个列,索引执行

  • 最左匹配原则
    指的是联合索引中,优先走最左边列的索引。对于多个字段的联合索引,也同理。如 index(a,b,c) 联合索引,则相当于创建了 a 单列索引,(a,b)联合索引,和(a,b,c)联合索引
    -最左前缀匹配,中间某个值没有匹配,先根据最左边的开始找,走索引,找到后在再这些数据中找
  • 如果是范围查询>=,<=between等操作,你只能符合最左匹配原则才可以范围,范围后的就不走索引了
  • 如果对某列用了函数这一列就不用索引了

5.mysql中事物的ACID:

  • 原子性(atomicity):一个原子事物要么完全执行成功,要么干脆不执行
  • 一致性(Consistency):执行事物前后,数据保持一致,例如转账,转账者和收款者的总额应该保持一致
  • 隔离性(Isolation):并发访问数据库,一个用户的事物不被其他事物所干扰,各并发事物之间数据库是独立的。
  • 持久性(Durability) 一个事物被提交之后,它对数据库中数据的改变是持久的,即使数据库发生了故障也不应该对其有任何的影响

6.mysql的事物隔离级别

  • 读未提交 read uncommitted
    所有事物都可以看到未提交事物的执行结果,在这种隔离级别会产生各种的问题,本隔离级别很少用于事件中,因为读取未提交的会产生脏读的情况。
  • 读已提交 read committed
    大多数数据库采用的隔离级别(但不是mysql采取的隔离级别) 只能看见别人已经提交的数据,当用户第一次查询结果,和第二次查询结果中间出现了一次提交数据修改,那第一次查询的结果和第二次查询的结果产生不一样的结果,这个隔离级别也支持所谓的’不可重复读’
  • 可重复读 repeatable read 可重复读
    Mysql使用当前的隔离级别,该级别解决了不可重复读的问题,同一个事物在并 发读取事物时,会看到同样的结果,不过这会导致一个棘手的问题幻读
  • Serializeble 可串行化,
    该为最高的隔离级别,它通过强制事物排序,使之不可互相冲突,从而解决幻读的问题,简言之,serializeble是在每个读取数据上加锁,在这个级别中可能导致Timeout与锁竞争Lock Contention ,实际项目中很少使用到这个级别,但如果用户的应用为了数据的稳定性,需要强制减少并发的话,也可以使用当前的隔离级别。
  • 幻读: 第一次读取数据可能是一行,但第二次再次读取数据的时候因为刚好有线程插入了数据,这时候数据变成了两行,从而出现第一次和第二次读取数据不一样的情况。

7.mysql的锁

按锁的粒度可分为:表锁、页面锁、行锁、记录锁、间隙锁、临键锁
按锁的属性可分为:共享锁、排它锁
按加锁机制可分为:乐观锁、悲观锁
  • innodb:的行锁:分为排他锁(x)和共享锁(s),当对数据进行,insert,delete,update的时候innodb会自动给数据加上行级的排他锁,当为select操作的时候,是不加锁的
  • 共享锁(又称读锁、S锁):
    作用:防止其他事务修改当前数据。
    加锁方式:

在select语句末尾加上lock in share mode关键字。

# 对id=1的用户加读锁
select * from user where id=1 lock in share mode;
  • 排他锁(又称写锁、X锁):
    作用:防止其他事务读取或者更新当前数据。
    加锁方式:
    在select语句末尾加上for update关键字。
# 对id=1的用户加写锁
select * from user where id=1 for update;

悲观锁:
总是假设别人会修改当前数据,所以每次读取的时候,总是加锁。然后就是一个人干事情别人都干不了。
适用于写多读少的场景。

# 加读锁
select * from user where id=1 lock in share mode;
# 加写锁
select * from user where id=1 for update;

乐观锁
总是假设别人不会修改当前数据,所以每次读取数据的时候都不会加锁,只是在更新数据的时候通过version判断别人是否修改过数据,Java的atomic包下的类就是使用乐观锁(CAS)实现的。

适用于读多写少的场景。

select id,name,age,version from user id=1;
update user set age=age+1 where id=1 and version=1;

8.mysql的优化过程:

如何处理慢查询问题:
1)开启慢查询日志,准确定位到那个sql语句出现了问题
2)分析sql语句,看是否查询了多余列的数据,对语句进行分析后重写
3)分析sql的执行计划,然后获取使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
4)如果对表中的优化已经无法进行,可以考虑表中的数据是否太大,如果是的话可以进行横向或者纵向的分表。

9.索引的设计原则是什么?

1)适合索引的列是出现在where语句中的列
2)基数较小的表,索引效果差,没必要创建索引
3)在选择索引的时候越短越好,没必要用全部字段做索引
4)定义有外键的数据列一定要建立索引
5)更新频率大的列,没必要创建索引
6)大文本、大对象不要创建索引。

你可能感兴趣的:(java面试题)