java多线程面试中常见知识点

1.进程和线程

  (1)进程是资源分配的最小单位,线程是程序执行的最小单位。

  (2)进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

  (3)线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

  (4)但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

  (5)线程只由堆栈寄存器,程序计数器,TCB(线程控制块)组成。

2.Thread中start和run方法的区别

  (1)  调用start()方法会创建一个新的子线程并启动。

  (2)  run()方法只是Thread的一个普通方法的调用。

3.如何给run()方法传参

(1)构造函数;

(2)成员变量(set和get);

(3)回调函数。

4.如何实现处理线程的返回值?

(1)主线程等待法;

(2)使用Thread类的join()阻塞当前线程以等待子线程处理完毕;(缺点:粒度不够细);

(3)通过Callable接口实现:通过FutureTask  OR  线程池获取。

5.sleep()和wait()的区别?

(1)sleep是Thread类的方法,wait是Object类中定义的方法(native);

(2)sleep方法可以在任何地方使用;

(3)wait方法只能在syn方法或同步快中使用;

(4)**sleep只会让出cpu,不会导致锁行为的改变;

(5)**wait不仅让出cpu,还会释放已经占有的同步资源。

6.Java中的锁池和等待池

  Java平台中,因为有内置锁的机制,每个对象都可以承担锁的功能。Java虚拟机会为每个对象维护两个“队列”(姑且称之为“队列”,尽管它不一定符合数据结构上队列的“先进先出”原则):一个叫Entry Set(入口集),另外一个叫Wait Set(等待集)。对于任意的对象objectX,objectX的Entry Set用于存储等待获取objectX这个锁的所有线程,也就是传说中的锁池,objectX的Wait Set用于存储执行了objectX.wait()/wait(long)的线程,也就是等待池。

7.notify()和notifyAll()的区别?

  notify 仅仅通知一个线程使其进入锁池,但我们不知道哪个线程会收到通知,然而 notifyAll 会通知所有等待中的线程。

8.yield

  Thread.yield()方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。

  yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

9.java中interrupt(),isInterrupted()和interrupted()

(1)当对一个线程,调用 interrupt() 时:

  ① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true。被设置中断标志的线程将继续正常运行,不受影响。

     ② 如果线程处于被阻塞状态(sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException

  interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行

  ① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程

  ② 在调用阻塞方法时正确处理InterruptedException异常

(2)interrupted()是静态方法:其作用是测试当前线程是否已经中断,内部实现是调用的当前线程的isInterrupted(boolean ClearInterrupted),并且会清除当前线程的中断状态。(ClearInterrupted参数可以用来控制是否清除中断标志)

(3)isInterrupted()是实例方法:isInterrupted()方法用来测试调用该方法的线程的中断状态,不会清除当前线程的中断状态。

10. CAS(Compare-and-Swap)

  

  通常提到并发编程,在数据的一致性上面,都会考虑用加锁方式在解决。在这里,对锁的解决方案就不多提了,其优势在于编程简单,java里面解决方案很多,synchronized关键字或者Lock都能提供原子化操作语意。但其劣势也非常明显,加锁其实并不耗时间,但是其容易造成资源等待,等待是非常耗时的,不合理的设计还容易造成死锁。更好的办法就是利用CAS机制实现乐观锁(每次不加锁去完成操作,如果因为冲突失败就重试,直到成功。)来解决效率问题。

  CAS是由CPU硬件实现,所以执行相当快.CAS有三个操作参数:内存地址,期望值,要修改的新值,当期望值和内存当中的值进行比较不相等的时候,表示内存中的值已经被别线程改动过,这时候失败返回,当相等的时候,将内存中的值改为新的值,并返回成功。

11. volatile

  volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。 

  volatile变量的特性:

  (1)保证可见性,不保证原子性
    a.当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
    b.这个写会操作会导致其他线程中的缓存无效。
  (2)禁止指令重排重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
            a.重排序操作不会对存在数据依赖关系的操作进行重排序。
    比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
    b.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变
      比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发 生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

12. happens-before

happens-before原则定义如下:

  (1)如果一个操作happens-before(之前发生)另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

  (2)两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

happens-before规则如下:
  (1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
  (2)锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
  (3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
  (4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
  (5)线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
  (6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  (7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  (8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

13. JMM

   JMM(java memory model)java内存模型主要目标是定义程序中的变量,(此处所指的变量是实例字段、静态字段等,不包含局部变量和函数参数,因为这两种是线程私有无法共享)在虚拟机中存储到内存与从内存读取出来的规则细节,Java 内存模型规定所有变量都存储在主内存中,每条线程还有自己的工作内存,工作内存保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在自己的工作内存中进行而不能直接读写主内存的变量,不同线程之间无法相互直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成。

14. ReentrantLock

  未完待续。。。

上一篇:linux下shutdown无法关闭tomcat进程的解决方式


下一篇:Angular企业级开发(8)-控制器的作用域