面试专题之——JUC
进程和线程的区别有哪些?
进程:进程独占内存空间,是并发程序在执行过程中分配和管理资源的基本单位
线程:是进程的一个执行单元,是cpu调度的最小单位,一个进程可以有多个线程,并且线程共享进程的内存资源,线程间切换更加快速,让进程子任务得以并发执行
进程和线程的关系是怎么样的?
1、运行一个程序会产生一个进程,进程中最少包含一个线程(主线程)
2、每个进程对应一个jvm实例,多个线程共享jvm中的堆
3、主线程可以创建子线程
并发和并行有什么区别?
并发:同时给CPU提交多个任务给它执行,然后由CPU决定每个线程的执行时间,同一时间只能执行一个任务,但是因为CPU很快所以感觉是同时运行的
并行:多个任务真正的同时执行,这个由CPU的内核数量决定的
什么叫做线程上下文切换?
在多线程中,CPU通过给每个线程分配执行时间来实现线程间的切换,当一个线程执行时间到期,该线程就会进入挂起等待状态,同时执行其他线程,因为CPU的速度很块所以给人的感觉是在同时执行,这之间的线程切换就是线程的上下文切换
有多少种方法创建线程?
1、Thread:继承Thread类,然后创建它
2、Runnable:实现Runnable接口并创建它的对象,然后创建Thread对象把之前创建的对象传给它
3、Callable:实现Callable接口并创建对象并重写call方法,然后创建线程池,然后创建线程对象然后用 submit 方法加入线程池或者创建 FutureTask 对象传入实现了 Callable 接口的对象,最后创建 Thread 对象传入 FutureTask 对象即可。不过它不是标准的Java定义方法,而是基于Future思想来完成
start和run的区别有哪些?
1、调用start()会创建一个新的子线程并且启动
2、run()方法只是对Thread实例中方法的调用
Thread和Runnable的关系有哪些?
1、Thread是一个类,Runnable是一个接口
2、Thread是一个实现了Runnable接口的类,使得run支持多线程
3、因为只能继承一个类,所以尽量使用Runnable接口
如何给run()方法传参?
1、构造方法传参
2、成员变量set方法传参
3、回调函数传参
如何实现获取子线程的返回值?
1、主线程等待法:主线程等待子线程完成后获取,但是等待的多了代码会臃肿
2、使用t.join(),阻塞当前线程,等待子线程t处理完毕
3、Callable接口实现,通过FutureTask 或者 线程池获取
FutureTask:首先实现Callable接口,重写call方法,创建这个对象传给FutureTask构造方法,最后创建Thread对象传入FutureTask对象
线程池:通过Executors获取ExecutorService线程池对象,然后调用submit()方法传入实现了Callable的对象
sleep和wait的区别有哪些?
1、sleep()是Thread类的方法,wait()是Object的方法
2、sleep()方法可以在任意地方使用,wait()只能在锁里面使用,因为wait()需要释放当前锁
3、Thread.sleep()只会让出cpu,不会释放同步锁;Object.wait()不仅让出cpu,还会释放已经占有的同步锁
notify和notifyAll区别有哪些?
notify:随机唤醒一个等待的线程去竞争获取锁的机会
notifyAll:唤醒所有等待的线程去竞争获取锁的机会
yield、interrupt、join方法有什么作用?
yield方法:当前线程让出cpu的使用权,但CPU有可能再次选中当前线程来运行
interrupt:通知线程该中断了,主要用于中断阻塞,我们一般想关闭一个线程都会设置一个开关,在线程内部使用Thread.currentThread().isInterrupt()判断线程是否需要中断,然后在外部线程调用interrupt方法终止线程,但线程中有sleep阻塞了或者wait等待了,就会立即抛出异常,结束阻塞或者等待,最后抛出异常
join:把当前线程从就绪状态进入到运行状态,它底层使用wait与notifyall实现,一般用不到,但面试可能会问如何保证线程执行的顺序,这时就要用到它,在创建多个线程时,每个新创建的线程都传入对上一个现成的引用,然后当前线程执行完直接调用上一个线程的join方法即可
线程的状态都有哪些?
1、新建
2、就绪(start)
3、运行(获得cpu执行权限)
4、阻塞(sleep) > 可运行
5、等待(wait) > 唤醒(notify) > 获得锁 > 可运行
6、死亡(run或main结束)
什么是守护线程?
守护线程就是会随着主线程的结束而结束的线程,它可以做一些辅助性的工作,例如GC垃圾回收。但是守护线程中的finally代码块不一定会执行,所以无法保证清理资源等操作一定会执行
Java是如何解决多线程并发问题的?什么是JMM?
Java使用了JMM来解决多线程并发问题,JMM定义了共享内存中多线程读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性,总之JMM让java程序与硬件指令进行了隔离。
JMM的内存模型是怎样的?
JMM把内存分成了2块,分别是主内存和工作内存,线程对变量操作时必须在工作内存中进行。操作时首先把主内存中的变量拷贝一份到工作内存中,操作完成后再写回主内存,总之线程跟线程之间是相互隔离的,线程跟线程交互需要通过主内存
Java并发编程有哪三大特性?
- 原子性:一个线程在CPU执行过程中不能中断,要么执行完成,要么不执行
- 内存可见性:对变量加上Volatile关键字修饰后,在多个线程访问同一个变量时,一个线程修改了该变量,其他线程能看到修改后的变量。因为加上Volatile关键字修饰之后,任何线程对该值进行写操作都会强制刷到主内存中,读取时都会去主内存拉取最新值
- 有序性:程序执行代码的顺序按照代码的先后顺序执行,因为CPU在执行代码时会对代码顺序进行优化,虽然在单线程中不会有问题,但在多线程中就有可能出现问题,所以Java多线程对此进行了优化
如何解决Volatile非原子性问题?
在一个变量用Volatile修饰后可以实现内存可见性,也就是多个线程获取到的值都是最新值,但问题出现了:某个变量值为5,当两个线程同时获取这个变量,线程1获取到变量值为5,把值+1,结果为6;线程2也获取到变量值为5,把值+1,结果为6。最终这个值为6,但实际我们对它进行了两次加操作,预期的结果为7,这就是Volatile的非原子性的问题。
如何解决?
- 使用synchronized修饰+1操作的方法
- 使用ReentrantLock可重入锁
- 使用AtomicInteger原子操作类
synchronized和volatile的区别有哪些?
1、volatile不需要加锁,比synchronized更轻便,不会阻塞线程
2、synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性
Lock(ReentrantLock可重入锁)锁与Synchronized有什么区别?
1、Synchronized是关键字,属于JVM层面,ReentrantLock是类,属于API层面
2、Synchronized不需要手动释放锁,ReentrantLock需要手动释放锁
3、Synchronized不可以中断,ReentrantLock可以中断
4、Synchronized只能是非公平锁,ReentrantLock还可以是公平锁
5、Synchronized只有一个阻塞队列,只能唤醒一个或全部,ReentrantLock可以实现分组唤醒,可以精准唤醒
什么是CAS?
CAS本质是修改内存中的值,它是一个基于硬件的原子性操作,他有三个参数:一个当前内存值V、旧的预期值A、即将更新的值B,当前内存值等于旧的预期值时,就把内存值改为新值,返回true,否则不做操作并返回false。它是一个先比较后修改的过程,用它来解决线程安全问题可以大大的提高性能,Atomic相关类都是用它来实现原子性操作的
CAS是如何在多CPU下实现原子性的?
- 总线加锁:当某个处理器要操作共享变量时在总线上加锁,但是这种方式容易造成阻塞,从而增加系统开销
- 缓存加锁:当某个处理器对共享变量进行操作时,其他处理器会有一个嗅探机制,将其他处理器的该共享变量的缓存失效,并且从主内存中获取最新的值
CAS有什么缺陷?
1、循环时间长,如果一直不成功则会一直进行自旋操作
2、只能操作一个共享变量,如果要操作多个变量就只能用锁了
3、ABA问题:另一个线程将内存中的值改变了,之后又改回来,此时对于当前线程来说是没有改变的。解决方法就是给变量加上版本号
什么是公平锁?什么是非公平锁?它两谁性能好?
公平锁:谁等待时间长谁获取锁
非公平锁:CPU时间片轮询到哪个线程,哪个线程就获取锁
公平锁因为要判断谁等待时间长,所以会增加线程唤醒的上下文切换时间,所以它的性能更差