面试专题之——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时间片轮询到哪个线程,哪个线程就获取锁

公平锁因为要判断谁等待时间长,所以会增加线程唤醒的上下文切换时间,所以它的性能更差