面试题:Java多线程

21 篇文章 2 订阅
2 篇文章 0 订阅

1、什么是进程,什么是线程?

何为进程?
进程是程序的一次执行过程,是系统运行程序的基本单元,因此进程是动态的,系统运行一个程序即是一个进程从创建,运行到消亡的过程。当我们运行Java程序的时候,系统会启动一个JVM进程,main函数就是我们的主线程,同时会启动一个GC线程既守护线程。所以一个java程序最少有两个线程。

何为线程?
线程是比进程更小的一个存在,是比进程更小的一个执行单元,一个进程可以包括多个线程,与进程不同的是,线程之间会共享一块内存空间和系统资源,在java程序中就是堆和方法区。但每个线程会有各自的程序计数器、虚拟栈、本地方法栈。所以线程创建比进程创建所消耗的资源更小。也正因为如此,线程也被称为轻量级进程。

2、请简要描述线程与进程的关系,区别及优缺点?

从JVM角度来看线程和进程的关系。

在这里插入图片描述可以看到一个进程有多个线程,线程共享堆和方法区,但线程私有的有程序计数器和虚拟机栈和本地方法栈。

区别是线程之间可以互相有联系,有共享的内存空间和系统资源,进程之间关系不大,线程执行开销小,但是多线程情况下可能会有内存溢出、死锁、资源调度不均匀之类的问题。

3、为什么程序计数器、虚拟机栈、本地方法栈是线程私有的?

程序计数器是记指令执行的行数和顺序的,上下文切换时候要保存住当前线程执行的点,所以每个线程有每个线程的程序计数器。虚拟机栈和本地方法栈存放的是该线程执行的方法,里面存放局部变量、常量引用等等,私有是为了保证局部变量不被别的线程访问到。

一句话简单了解堆和方法区
堆和方法区都是线程共享的资源,堆里面存放的是新创建的对象,所有的对象都在这里分配内存,方法区存放的是类信息、静态变量、常量、即时编译器编译的热点代码等等。

4、说说并发与并行的区别?

并发是指在一个时间段内,多个线程同时执行。
并行是指在一个时间点上,多个线程同时执行。

5、为什么要使用多线程呢?多线程会产生什么问题?

多线程可以提高程序的执行效率,多个人干活比一个人会更快。但多线程可能会产生内存溢出、死锁、系统资源调度不均匀、上下文切换消耗系统资源等问题。

6、说说线程的生命周期和状态?

在这里插入图片描述
线程在创建之后就进入新建状态,调用start()方法后进入就绪状态,当轮到该线程执行的时候进入运行状态,遇到线程调用资源需要等待的时候进入阻塞状态,调用wait方法进入等待状态,调用sleep(long)和wait(long)后进入超时等待状态,线程执行任务完成后进入终止状态。

7、什么是上下文切换?

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

8、什么是线程死锁?如何避免死锁?

当A线程需要用到B线程所占用锁的资源,B线程需要用到A线程所占用锁的资源,双方互相等待,导致死锁。

死锁避免有三个方法:1、加锁顺序,加锁做好顺序,防止死锁。2、加锁时限,线程获得锁的时间受限制,比如只有5秒就得释放锁。3、死锁检测,比如A线程要拿B线程所占用锁的资源,在等待10秒后发现拿不到,就让B线程的锁释放,再让自己线程的锁释放掉。

9、说说 sleep() 方法和 wait() 方法区别和共同点?

wait方法会释放锁,sleep方法不会释放锁。
两者都会让线程暂停运行。
wait方法被调用后,需要同一个对象的另外一个线程调用notify或者notifyall方法。

10、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

当我们new 一个Thread的时候,线程进入新建状态,调用了start方法后进入可运行状态,当线程进入执行状态就会自动调用run方法,如果直接调用run方法,程序会认为是main函数调用一个普通的run方法。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

11、说一说自己对于 synchronized 关键字的了解

synchronized是为了解决多个线程访问同个资源的同步性,使用synchronized可以实现一个方法或者代码块只有一个线程执行。
synchronized是非公平锁、可重入锁、底层使用monitor对象实现锁机制。

12、说说自己是怎么使用 synchronized 关键字,在项目中用到了吗

在实现单理模式的时候使用了双重锁机制+volatile关键字实现。在一些方法需要实现只有一个线程进入的时候也会使用synchronized。

13、谈谈 synchronized和ReentrantLock 的区别

1、 两者都是可重入锁
2、synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
3、ReentrantLock可以实现公平锁、Condition等高级功能

14、 volatile关键字

在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
在这里插入图片描述

要解决这个问题,就需要把变量声明为volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。

说白了, volatile 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。

在这里插入图片描述

15、并发编程的三个重要特性

原子性 : 一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。synchronized 可以保证代码片段的原子性。

可见性 :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。volatile 关键字可以保证共享变量的可见性。

有序性 :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。

16、说说 synchronized 关键字和 volatile 关键字的区别

volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
volatile关键字能保证数据的可见性,但不能保证数据的原子性;synchronized关键字两者都能保证。
volatile关键字主要用于解决变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

17、ThreadLocal是什么?

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

再举个简单的例子:

比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来避免这两个线程竞争的。

18、 为什么要用线程池?

池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:

降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值