java开发面试问答----多线程篇

1,096 阅读4分钟

创建多线程的方式

1)继承Thread类创建线程

2)实现Runnable接口创建线程

3)使用Callable和Future创建线程

4)使用线程池

start()和run()的区别

run()是线程对象执行的内容,start()是启动线程对象

如何创建一个线程池Excutorservice

  • 核心线程数
    • 当池中线程数小于核心线程数时创建新线程
    • 核心线程数不会被回收
  • 最大线程数
    • 当池中线程数大于核心线程数小于最大线程数且队列满时创建新线程
    • 大于核心线程数小于最大线程数的线程在空闲时间超过超时时间后会被回收
  • 阻塞队列
    • 当池中线程数大于等于核心线程数时任务会优先放入队列中
  • 常用的4种线程池
    • fixedThreadPool: 核心线程数和最大线程数定长,多的任务放入阻塞队列中
    • cachedThreadPool: 核心线程数为0,最大线程数无限大,线程超过空闲时间时回收
    • scheduledThreadPool: 核心线程数定长,最大线程数无限大,线程执行完任务即回收,可周期性执行
    • singleThreadPool: 单线程,任务放入阻塞队列中,用于顺序执行

多线程同步有哪几种方法

  • synchronized
  • lock
  • CountDownLatch

区别

  • synchronized是jvm层面的,而lock是第三方的类,都是可重入锁
  • lock可以判断锁的状态,trylock可以设置超时,lockInterruptibly可以中断等待

synchronized锁代码块和锁方法有什么区别

  • 修饰方法
    • 修饰一般方法,锁住的是对象,不同的对象是不同的锁
    • 修饰静态方法,锁住的是类,该类的所有对象共享这把锁
    • 对象被锁时可以调用该对象的非同步方法
  • 修饰代码块
    • 作用范围是括号中的范围
    • this: 锁住的是对象
    • *.class: 锁住的是类

锁有哪些状态(sleep和wait有什么区别)

  • 锁的状态
    • 准备状态
    • 就绪状态
    • 运行状态
    • 阻塞状态
      • wait: object类,当前线程释放了对象monitor变成挂起状态,可用notify唤醒去抢夺对象monitor,多个线程等待时,notify只能随机唤醒一个,全唤醒需要使用notifyAll
      • sleep: Thread类中,没有释放对象monitor,当前线程睡眠状态,时间到后自动唤醒
    • 死亡状态
  • ReentrantLock
    • lock的具体实现,是可重入锁(同一个对象可重复递归调用的锁)
    • 公平锁是线程获取锁的顺序是按照加锁顺序,非公平锁则是抢锁机制

什么是乐观锁,什么是悲观锁

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,如synchronized
  • 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。如atomic包下面的原子变量,使用了volatile原语,实现方式是CAS:
    • V(需读写的内存位置)+A(准备用来比较的参数)+B(准备写入的新值),若A的参数与V的对应的值相匹配,就写入值B;若不匹配,就写入这个不匹配的值而非B。

一致性hash算法

一致性hash采用的是环状结构,hash出来的key落到顺时针最近的结点上,这样可以保证在结点增加删除的情况下影响最小,而为了应对平衡性问题采用了虚拟结点的方式,将实际结点数放大,使key更均匀的落到每个结点上。

java线程池里面的arrayblockingqueue,linkedblockingqueue的用途和区别

  • arrayblockingqueue是使用数组实现的必须指定长度,为了达到环,当索引下标等于数组长度时会归0,put和take操作共用同一把锁,数据的写入和读取只移动索引。
  • linkedblockingqueue是使用链表实现的,每次写入会新增一个node放到链表尾部,读取则从表头删除node,由于put和take单独操作分别使用各自的锁,在效率上比arrayblockingqueue要高。