1.为什么要使用并发编程
提升多核CPU的利用率:一般来说一台主机上会有多个cpu核心,我们可以创建多个线程,理论上讲操作系统可以将多个线程分配给不同的CPU执行,每个CPU执行一个线程,这样就提高了CPU的使用效率。
简单来说:
- 充分利用多核CPU的计算能力;
- 方便进行业务拆分,提升应用性能
2.并发编程有什么缺点
并发编程的目的就是为了提高程序的执行效率,提高程序的运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题。上下文切换,线程安全,死锁等问题
3.并发编程三个必要因素
原子性:原子性指一个或多个操作要么全部执行成功,要么全部执行失败。
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
有序性:程序的执行顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)。
4.在Java程序中怎么保证多线程的运行安全
出现线程安全问题的原因一般都是三个原因:
线程切换带来的原子性问题,解决办法:使用多线程之间的同步synchronized或者使用锁lock
缓存导致的可见性问题,解决办法:synchronized,Lock,volatile,可以解决可见性问题。
编译优化带来的有序性问题,解决办法,Happens-Before规则可以解决有序性问题。
5.并行和并发的区别
并行:同一时间多个处理器同时处理多个任务
并发:单位时间,一个处理器处理多个任务,按时间片轮流处理多个任务。
6.什么是上下文切换
多线程编程中一般线程的个数大于CPU核心的个数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略是为每个线程分配时间片并轮转的形式,当一个线程时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
7.守护线程和用户线程有什么区别
用户线程:运行在前台,执行具体的任务,如程序的主线程,连接网络的子线程等都是用户线程
守护线程:运行在后台,为其他前台线程服务。也可以说守护线程是JVM中非守护线程的佣人。一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作。
8.什么是线程死锁,死锁相关面试题
什么是死锁?
死锁是指两个或者两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时系统产生了死锁。
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期的阻塞,因此程序不可能正常终止。
举例:哲学家就餐问题
形成死锁的四个必要条件?
互斥条件:在一段时间内某资源只能由一个线程占用。如果此时还有其他进程请求资源,就只能等待,直到占有资源的进程用完释放。
占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程阻塞,但又对自己已获得的其他资源保持不放。
不可抢占资源:别人已经占有了某项资源,你不能因为自己也需要该资源,就去吧别人的资源抢过来。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(哲学家就餐问题)
如果避免死锁?
避免一个线程同时获得多个锁
避免一个线程在锁内同时占有多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁,使用lock.tryLock(timeOut)来替代使用内部锁机制
9.Java线程有几种状态
在Java thread state枚举类中定义了6种线程状态,它们分别是,新建状态NEW,运行状态Runnable,阻塞状态Blocked,等待状态Waiting,延迟等待状态Timed Waiting和终止状态
Terminated。
10.Java创建线程的方式
继承Thread类
实现Runnable接口
实现Calllable接口,并结合Future来创建线程
11.说一下runnable和callable的区别
相同点:
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
不同点:
Runnable接口run方法没有返回值,Callable接口call方法有返回值;是个泛型,和Future或者FutureTask配合可以用来获取异步执行的结果。
Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以捕获异常信心。Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主线程的继续往下执行,如果不调用不会阻塞。
12.Future和FutureTask的区别
Future
是一个接口,它表示一个异步计算的结果。- 通过
Future
可以提交一个任务,并在将来的某个时候获取任务的执行结果。 Future
提供了get()
方法用于获取任务执行的结果,该方法会阻塞当前线程直到任务执行完成并返回结果。Future
还提供了isDone()
和cancel()
等方法用于检查任务是否已经完成和取消任务的执行。
FutureTask
是Future
接口的一个实现类,它同时实现了Runnable
接口,因此可以作为任务提交给线程池执行。FutureTask
可以用来包装一个 Callable 或者 Runnable 对象,使其具有异步执行和获取执行结果的能力。FutureTask
提供了get()
方法用于获取任务执行的结果,同样会阻塞当前线程直到任务执行完成并返回结果。FutureTask
还提供了cancel()
方法用于取消任务的执行,以及isDone()
等方法用于检查任务是否已经完成。
总的来说,Future
接口是用来表示异步计算的结果,而 FutureTask
类是 Future
接口的一个实现,它提供了更多的功能,如可以作为任务提交给线程池执行,并且可以直接包装 Callable 或者 Runnable 对象。
13.sleep()和wait()的区别
类的不同:sleep是Thread线程类的静态方法,wait是Object类的方法。
是否释放锁:sleep不释放锁,wait释放锁
用途不同:wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。
用法不同:wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一对象上的notify()或者notifyAll()方法。sleep方法执行完后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
14.为什么线程通信的方法wait(),notify(),notifyAll()被定义在Object类里?
因为java中所有的类都继承了Object类,Java想让任何对象都可以作为锁,并且wait,notify等方法用于等待对象的锁或者唤醒线程,在Java的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。
15.为什么方法wait(),notify(),notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到有其他线程调用这个对象的notify或者notifyAll方法。同样的,当一个线程调用对象的notify或者notifyAll方法的时候,它就会释放这个对象的锁,以便其他线程可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以它们只能在同步方法或者同步块中被调用。