14个必问的 多线程&并发 面试题

java中你知道哪些锁?

问题回答
乐观锁/悲观锁
共享锁/独享锁
公平锁/非公平锁
互斥锁/读写锁
重入锁
自旋锁
分段锁
偏向锁/轻量级锁/重量级锁

辅助理解:
在这里插入图片描述

Java线程的状态或者生命周期

问题回答

  • Java的线程状态被定义在公共枚举类java.lang.Thread.state中。一种有六种状态
    1.新建(NEW):表示线程新建出来还没有被启动的状态,比如:Thread t = new MyThread();
    2.就绪/运行(RUNNABLE):该状态包含了经典线程模型的两种状态:就绪(Ready)、运行(Running):
    3.阻塞(BLOCKED):通常与锁有关系,表示线程正在获取有锁控制的资源,比如进入synchronized代码块,获取ReentryLock等;发起阻塞式IO也会阻塞,比如字符流字节流操作。
    4.等待(WAITING):线程在等待某种资源就绪。
    5.超时等待(TIMED_WAIT):线程进入条件和等待类似,但是它调用的是带有超时时间的方法。
    6.终止(TERMINATED):线程正常退出或异常退出后,就处于终结状态。也可以叫线程的死亡。

  • 经典线程模型包含5种状态,:新建、就绪、运行、等待、退出

  • 经典线程的就绪、运行,在java里面统一叫RUNNABLE

  • Java的BLOCKED、WAITING、TIMED_WAITING都属于传统模型的等待状态

synchronized 与lock区别?

问题回答
1.lock是一个接口,而synchronized是java的一个关键字
2.synchronized异常会释放锁,lock异常不会释放,所以一般try catch包起来,finally中写入unlock,避免死锁。
3.Lock可以提高多个线程进行读操作的效率
4.synchronized关键字,可以放代码块,实例方法,静态方法,类上
5.lock一般使用ReentrantLock类做为锁,配合lock()和unlock()方法。在finally块中写unlock()以防死锁。
6.jdk1.6之前synchronized低效。jdk1.6之后synchronized高效。

synchronized 与ReentrantLock区别?

问题回答
1.synchronized依赖JVM实现,ReentrantLock是JDK实现的。synchronized是内置锁,只要在代码开始的地方加synchronized,代码结束会自动释放。Lock必须手动加锁,手动释放锁。
2.ReenTrantLock比synchronized增加了一些高级功能。synchronized代码量少,自动化,但扩展性低,不够灵活;ReentrantLock扩展性好,灵活,但代码量相对多。
3.两者都是可重入锁。都是互斥锁。
4.synchronized是非公平锁,ReentrantLock可以指定是公平锁还是非公平锁。

synchronized 与ThreadLocal区别?

1.都是为了解决多线程中相同变量的访问冲突问题。
2.Synchronized同步机制,提供一份变量,让不同的线程排队访问。
3.ThreadLocal关键字,为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
4.ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

synchronized 与volatile区别?

1.volatile是一个类型修饰符(type specifier)。
2.volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。
3.关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且只能修改变量,而synchronized可以修饰方法,以及代码块。
4.多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步
5.关键字volatile解决的下变量在多线程之间的可见性;而synchronized解决的是多线程之间资源同步问题

Thread类中的start()和run()方法有什么区别?

1.通过调用线程类的start()方法来启动一个线程,使线程处于就绪状态,即可以被JVM来调度执行,在调度过程中,JVM通过调用线程类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就会终止。
2.如果直接调用线程类的run()方法,会被当作一个普通的函数调用,程序中仍然只有主线程这一个线程。即start()方法能够异步的调用run()方法,但是直接调用run()方法却是同步的,无法达到多线程的目的。
3.因此,只用通过调用线程类的start()方法才能达到多线程的目的。

事务的隔离级别及引发的问题
  • 4个隔离级别
  • 读未提交、读已提交、可重复读、串行化
  • 分别怎么理解呢?
    读未提交(READ UNCOMMITTED),事务中的修改,即使没有提交,对其它事务也是可见的。
    读已提交(READ COMMITTED),一个事务能读取已经提交的事务所做的修改,不能读取未提交的事务所做的修改。也就是事务未提交之前,对其他事务不可见。
    可重复读(REPEATABLE READ),保证在同一个事务中多次读取同样数据的结果是一样的。
    串行化(SERIALIZABLE),强制事务串行执行。
  • 读已提交是sql server的默认隔离级别。
  • 可重复读是mysql的默认隔离级别。
  • 多个事务,各个隔离级别引起的问题
    读未提交:可能出现脏读、不可重复度、幻读;
    读已提交:可能出现不可重复度、幻读;
    可重复读:可能出现幻读;
    串行化:都没问题;
什么是线程安全,java如何保证线程安全?
  • 在多线程环境中,能永远保证程序的正确性。执行结果不存在二义性。说白了,运行多少次结果都是一致的。
  • 换种说法,当多个线程访问某一个类(对象或方法)时,这个类始终都能表现心正确的行为,那么这个类(对象或方法)就是线程安全的。
  • 使用synchronized关键字和使用锁。
介绍一下线程池?
  • 线程池就是预先创建一些线程,它们的集合称为线程池。
  • 线程池可以很好地提高性能,在系统启动时即创建大量空闲的线程,程序将一个task给到线程池,线程池就会启动一条线程来执行这个任务,执行结束后,该线程不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
  • 线程的创建和销毁比较消耗时间,线程池可以避免这个问题。
  • Executors是jdk1.5之后的一个新类,提供了一些静态方法,帮助我们方便的生成一些常见的线程池
    newSingleThreadExecutor:创建一个单线程化的Executor。
    newFixedThreadPool:创建一个固定大小的线程池。
    newCachedThreadPool:创建一个可缓存的线程池
    newScheduleThreadPool:创建一个定长的线程池,可以周期性执行任务。
  • 我们还可以使用ThreadPoolExecutor自己定义线程池,弄懂它的构造参数即可
    int corePoolSize,//核心池的大小
    int maximumPoolSize,//线程池最大线程数
    long keepAliveTime,//保持时间/额外线程的存活时间
    TimeUnit unit,//时间单位
    BlockingQueue workQueue,//任务队列
    ThreadFactory threadFactory,//线程工厂
    RejectedExecutionHandler handler //异常的捕捉器

简要回答

  • 线程池就是预先创建一些线程
  • 线程池可以很好地提高性能
  • 线程池可以避免线程的频繁创建和销毁
  • Executors可以创建常见的4种线程(单线程池、固定大小的、可缓存的、可周期性执行任务的)。
    可以通过ThreadPoolExecutor自己定义线程池。
同步和异步有何异同?
  • 同步发了指令,会等待返回,然后再发送下一个。
  • 异步发了指令,不会等待返回,随时可以再发送下一个请求
  • 同步可以避免出现死锁,读脏数据的发生
  • 异步则是可以提高效率
  • 实现同步的机制主要有临界区、互斥、信号量和事件
如何异步获取多线程返回的数据?

问题包含
说一下Callable这个接口的理解?
说一下Future接口的理解?
说一下FutureTask类的理解?
说一下CompletionService接口的理解?

问题回答

  • 通过Callable+Future,Callable负责执行返回,Future负责接收。Callable接口对象可以交给ExecutorService的submit方法去执行。
  • 通过Callable+FutureTask,Callable负责执行返回,FutureTask负责接收。FutureTask同时实现了Runnable和Callable接口,可以给到ExecutorService的submit方法和Thread去执行。
  • 通过CompletionService,jdk1.8之后提供了完成服务CompletionService,可以实现这样的需求。
  • 注意,实现Runnable接口任务执行结束后无法获取执行结果。
如何自定义线程池?

问题回答

  • 通过ThreadPoolExecutor创建
  • 弄懂它的7个构造参数即可
    int corePoolSize,//核心池的大小
    int maximumPoolSize,//线程池最大线程数
    long keepAliveTime,//保持时间/额外线程的存活时间
    TimeUnit unit,//时间单位
    BlockingQueue workQueue,//任务队列
    ThreadFactory threadFactory,//线程工厂
    RejectedExecutionHandler handler //异常的捕捉器
工作中哪些地方使用了多线程?
  • 一般业务,web层–> service层 -->dao --> sql基本用不到多线程
  • 数据量很大(1000w级别、TB级别)的I/O操作,可以考虑多线程
  • 举一些例子
    自己做并发测试的时候,假如想写想模拟3000个并发请求。
    多线程下单抢单,假如支持5000人的并发下单。
    多线程写入mysql,假如有1000w条数据要入库。
    多线程写入redis,假如有1000w的数据要存入redis。
    多线程导入ES索引,假如有1000w的数据要添加到ES索引。
    poi多线程导出,假如xls里面有10w的数据需要导出。
    poi多线程导入,假如有10w条数据需要导入到xls。
    多线程发送邮件,假如有10w用户需要发送邮件。
    多线程发送短信,假如有10w用户需要发送邮件。
    多线程备份日志,假如10tb日志文件要备份。
    多线程验证数据,比如验证url是否存在,假如有100w个url。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值