3. 高频面试题-多线程

高频面试题-多线程

(3) 创建线程的方式

  • 继承Thread

  • 实现Runnable接口

  • 实现Callable接口

  • 区别:

      1. 实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
      1. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
      1. Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

(4) 线程的生命周期,线程状态以及API怎么操作会发生这种转换;

(5) 一个线程连着调用start两次会出现什么情况?

  • 线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误。

(6) wait方法能不能被重写,wait能不能被中断;

  • wait时间到或被中断唤醒,不一定会继续执行或者跳到catch里,而是还需要等待获得锁。
    如果wait时间到或被中断唤醒,而T2还在syn里,那么T1还是会等待。

  • wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,这些方法都只能在同步方法或同步块中调用

  • sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。

(7) 线程池的实现原理?四种线程池?重要参数及原理?任务拒接策略有哪几种?

  • 线程池内部维护了一个线程列表,我们使用线程池只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行.jdk1.5引入Executor线程池框架Executor线程池框架,有以下四种实现
  • newFixedThreadPool():初始化一个指定线程数的线程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作为阻塞队列。

    特点:即使当线程池没有可执行任务时,也不会释放线程。

  • newCachedThreadPool():初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;

    特点:在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;

  • newSingleThreadExecutor():初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。

    如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行

  • newScheduledThreadPool():初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。
  • 提交任务给线程池
    • Executor.execute(Runnable command);
    • ExecutorService.submit(Callable task);
  • 线程池的关闭
    • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
    • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

总结:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果阻塞队列满了,那就创建新的线程执行当前任务;直到线程池中的线程数达到maxPoolSize,这时再有任务来,只能执行reject()处理该任务;

(10) 说说 CountDownLatch、CyclicBarrier 原理和区别

  • CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行, await()可以让线程等待,countDown() 每调用一次就减1.指定减到0所有等待的线程就开始执行。
  • CyclicBarrier是一个同步的辅助类,允许一组线程相互之间等待,达到一个共同点,再继续执行。
    当所有线程都准备好再一起执行。

(13) 多个线程同时读写,读线程的数量远远⼤于写线程,你认为应该如何解决并发的问题?你会选择加什么样的锁?

采用Read-Write Lock Pattern是一种将对于共享资源的访问与修改操作分离,称为读写分离。用单独的线程来处理读写,允许多个读线程同时读,但是不能多个写线程同时写。ReadWriteLock锁

前置处理(获取锁定)

try{

 	实际操作
}finally{
	后续处理(释放锁)
}

(16) wait/notify/notifyAll⽅法需不需要被包含在synchronized块中?这是为什么?

  • 需要
  • 因为wait/notify/notifyAll需要通过监视器操作对象锁。达到对对象锁得释放和获取

(17) ExecutorService你一般是怎么⽤的?是每个Service放一个还是个项目放一个?有什么好处?

  • Java线程池ExecutorService
      如果有一套相同逻辑的多个任务的情况下,应用一个线程池是个好选择。
      如果项目中有多套不同的这种任务,那每套任务应该一个线程池不是很正常的吗

(18) 两个线程设计题。记得一个是:t1,t2,t3,让t1,t2执行完才执行t3,原生实现。

– 使用线程的join() 方法控制

(8) synchronized 与 lock 的区别

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

(3) 产生死锁的四个条件(互斥、请求与保持、不剥夺、循环等待)

(10) CAS无锁的概念、乐观锁和悲观锁

  • CAS(比较与交换,Compare and swap) 是一种有名的无锁算法,无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)
  • 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

(11) Java中有哪些同步方案(重量级锁、显式锁、并发容器、并发同步器、CAS、volatile、AQS等)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值