JAVA常见笔试面试总结(二)(线程)

  1. 程序,进程,线程的区别
    答:
    程序:就是一段静态的代码
    进程:正在运行的一段程序
    线程:进程内的一个执行单元,若一个进程中包含多个线程,那么这段程序就是多线程的

  2. JAVA中实现多线程的3种方式
    答:
    一.继承Thread类,重写run()方法,启动线程时调用start()方法,start()方法有两个作用:启动线程,调用相应的run()方法
    二.实现Runnable接口,优点:避免啦java单继承的局限性,如果多个线程公用一个资源,使用此方法更合适,继承Thread需要将资源变成静态
    三.实现Callable接口,可以看作是Runnable接口的补充,Runnable接口没有返回值,而Callable有返回值

  3. 线程中的一些方法
    yield():调用此方法的线程会释放CPU(但是不能确定接下来执行的线程,因为仅仅是释放,不能确定接下来谁抢到CPU),该方法不释放锁
    join():调用该方法的线程一定会被执行,一直到结束
    sleep():显示的让当前线程睡眠?毫秒,该方法不释放锁

  4. 线程调度
    分为两种,时间片和抢占式,其中抢占式与优先级有关,1到10,10最大,但并不是10就一定可以抢到cpu

  5. 线程通信的一些方法
    wait():该方法会释放锁,并让线程等待
    notify():解除wait()状态,唤醒正在等待同步资源的线程中优先级最高的
    notifyAll():唤醒所有线程
    以上三个方法都属于Object类

  6. 线程的生命周期
    在这里插入图片描述

  7. 线程的同步
    一.线程同步的意义:在多个线程同时访问一个资源的时候,会出现资源分配错误或出现不合理资源的情况,比如,买票,就会出现-1张的问题,所以需要使用线程同步,避免以上问题
    二.线程同步的作用:在一个线程访问资源的时候,其他线程就算抢占到CPU也不能访问该资源,必须等待获取同步锁
    java中线程同步有两种方法:同步方法同步代码块

//这里写一个同步代码块
synchronized(同步监视器){
	//操作共享数据的代码
}

同步监视器可以是任何一个类的对象,也可以用this,使用继承方式实现的线程尽量不使用this
8. 死锁
不同的线程分别占用对方需要的同步资源,都在等待对方放弃自己需要的同步资源,就形成啦线程的死锁
9. 死锁的解决方案
加锁顺序:确保所有线程都按照同样顺序获得锁
加锁时限:在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁
死锁检测
10. 在 Java 程序中怎么保证多线程的运行安全
使用安全类,使用自动锁synchronized,使用手动锁Lock
11.多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
12.ThreadLocal 是什么?有哪些使用场景?
ThreadLocal从本质上讲,无非是提供了一个“线程级”的变量作用域,它是一种线程封闭(每个线程独享变量)技术,更直白点讲,ThreadLocal可以理解为将对象的作用范围限制在一个线程上下文中,使得变量的作用域为“线程级”。

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。(这里需要注意,ThreadLocalMap对象是属于Thread的,并不是属于ThreadLocal的)

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

ThreadLocal 的经典使用场景是数据库连接和 session 管理等。
此处引用啦ConstXiong的博客
13.原子性,有序性,可见性
原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行,注:只有x=3才具有原子性,x=y没有原子性(概念:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行)
有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”(概念:即程序执行的顺序按照代码的先后顺序执行)
可见性:对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去主存中读取新值 (概念:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值)
14.synchronized 和 volatile 的区别是什么?
一.volatile 是变量修饰符;synchronized 是修饰类、方法、代码段
二.volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性
三.volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞
四.volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化
15.synchronized 和 Lock 有什么区别?
一.synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁
二.synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁
三.通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到
16.synchronized 和 ReentrantLock 区别是什么?
一.ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作
二.ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁
三.ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等
17.java中常见的4种线程池
newCachedThreadPool:可缓存线程池,当线程池中的线程都处于活动时(全满),线程池会创建新的线程来处理新的任务,线程池中的空闲线程都有超时机制,默认超时时长为60s,超过60s的空闲线程就会被回收,比较适合执行大量耗时较小的任务
newFixedThreadPool:定长线程池,可以控制线程最大并发数量,超出的线程会在队列中等待,它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来
newScheduledThreadPool:定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor:单线程化的线程池,它只会用唯一的工作线程来执行任务,它确保所有的任务都在同一个线程中按顺序执行
18.线程池的任务拒绝策略
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作
CallerRunsPolicy 策略:由调用线程处理该任务
DiscardOleddestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务
DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理
19.线程池中submit()和execute()的区别?
execute()适用于不需要关注返回值的场景,只能执行Runnable类型的任务
submit()适用于需要关注返回值的场景,可以执行Runnable和Callable类型的任务
20.自定义线程池需要关注的参数
corePoolSize:线程池大小,决定着新提交的任务是新开线程去执行还是放到任务队列中,也是线程池的最最核心的参数。一般线程池开始时是没有线程的,只有当任务来了并且线程数量小于corePoolSize才会创建线程。
maximumPoolSize:线程池可以创建线程的最大数量
keepAliveTime:在线程数量超过corePoolSize后,多余空闲线程的最大存活时间。
unit:线程池维护线程所允许的空闲时间的单位
workQueue:线程池所使用的缓冲队列
handler:线程池对拒绝任务的处理策略
21.线程池基本运行过程
一.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
二.当调用 execute() 方法添加一个任务时,线程池会做如下判断:
​ a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
​ b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
​ c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
​ d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
三.当一个线程完成任务时,它会从队列中取下一个任务来执行。
四.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
22.ThreadPoolExecutor中排队的三种通用策略
直接提交:工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁(解释:什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中)。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。重点由于该SynchronousQueue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加

无界队列:使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

有界队列:当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
23.线程池的常见状态
RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
24.并行和并发有什么区别?
并行:多个处理器或多核处理器同时处理多个任务
并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行
25.守护线程是什么?
守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程
26.说一下 synchronized 底层实现原理?
synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值