Java 线程 面试题

并行和并发有什么区别?
  • 并发:一个处理器可以同时处理多个任务。这是逻辑上的同时发生。
  • 并行:多个处理器同时处理多个不同的任务。这是物理上的同时发生。
线程和进程的区别?
  • 进程:就是正在进行的程序,进程是操作系统控制的基本运行单元;
  • 线程:进程中独立运行的子任务就是一个线程。
为什么要使用多线程
  • 单任务:
    – 任务1执行10秒,10秒之后,再可以执行任务2.
    – 任务2必须等待任务1执行完成后,才可以执行,系统运行效率大大降低
    – 单任务的特点就是排队执行,也就是同步执行
  • 多任务:
    – cpu就可以在任务1 和任务2 之间来回切换,任务2 就不必等待10秒之后执行
    – 系统运行效率大大提高
    – 使用多线程也就是使用异步
守护线程是什么?
  • 在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
  • Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
  • User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
  • 值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。下面的方法就是用来设置守护线程的。
多线程有几种实现方式?
  • 继承Thread类:实现方式很简单,只需要创建一个类去继承Thread类然后重写run方法,在main方法中调用该类实例对象的start方法即可实现多线程并发。
  • 实现Runnable接口:这种方式的实现也很简单,就是把继承Thread类改为实现Runnable接口。
  • 使用Callable和Future创建线程:
Thread与Runnable、Callable的区别?
  • Thread是最简洁方便的,直接就可以start,不需要任何转换。
  • 但是Thread有一个很不好的地方就是继承了Thread类后由于java的单继承机制,就不可以继承其他的类了,而如果实现的是接口,就可以实现多个接口,使开发更灵活。
说一下 Runnable和 Callable有什么区别?
  • Runnable相对Callable方式来说代码更简洁,使用更方便,少了一次转换。
  • Callable方法有两个优点:有返回值、可以抛出异常。
线程有哪些状态?
  • 新建状态:即单纯地创建一个线程,创建线程有三种方式,在我的博客:线程的创建,可以自行查看!
  • 就绪状态:在创建了线程之后,调用Thread类的start()方法来启动一个线程,即表示线程进入就绪状态!
  • 运行状态:当线程获得CPU时间,线程才从就绪状态进入到运行状态!
  • 阻塞状态:线程进入运行状态后,可能由于多种原因让线程进入阻塞状态,如:调用sleep()方法让线程睡眠,调用wait()方法让线程等待,调用join()方法、suspend()方法(它现已被弃用!)以及阻塞式IO方法。
  • 死亡状态:run()方法的正常退出就让线程进入到死亡状态,还有当一个异常未被捕获而终止了run()方法的执行也将进入到死亡状态!
sleep() 和 wait() 有什么区别?
  • sleep方法是Thread类的静态方法,而wait()是Object超类的成员方法。
  • sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。
  • sleep方法需要抛异常,而wait方法不需要。Thread类中sleep方法就已经进行了抛异常处理。
  • sleep方法可以在任何地方使用,而wait方法只能在同步方法和同步代码块中使用。
notify()和 notifyAll()有什么区别?
  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁
线程的 run() 和 start() 有什么区别?

区别:start是创建并启动一个线程,而run是要运行线程中的代码

  • 启动一个线程是start()方法。
  • 启动线程之后start()方法会去调用run方法内容。
创建线程池有哪几种方式?
  • newFixedThreadPool:定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程。
  • newCachedThreadPool:可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制。
  • newScheduledThreadPool:定长线程池,可执行周期性的任务。
  • newSingleThreadExecutor:单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行。
  • newSingleThreadScheduledExecutor:单线程可执行周期性任务的线程池。
  • ewWorkStealingPool:任务窃取线程池,不保证执行顺序,适合任务耗时差异较大。
线程池都有哪些状态?
  • RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  • TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
  • TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
线程池中 submit() 和 execute() 方法有什么区别?
  • execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)。
  • execute() 没有返回值;而 submit() 有返回值。
  • submit() 的返回值 Future 调用get方法时,可以捕获处理异常。
在 Java 程序中怎么保证多线程的运行安全?
  • 线程的安全性问题体现在:
    原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性。
    可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。
    有序性:程序执行的顺序按照代码的先后顺序执行。

  • 导致原因:
    缓存导致的可见性问题。
    线程切换带来的原子性问题。
    编译优化带来的有序性问题。

  • 解决办法:
    JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题。
    synchronized、volatile、LOCK,可以解决可见性问题。
    Happens-Before 规则可以解决有序性问题。

  • Happens-Before 规则如下:
    程序次序规则:在一个线程内,按照程序控制流顺序,书写在前面的操作先行发生于书写在后面的操作。
    管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
    volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
    线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
    线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。
    线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
    对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

多线程中 synchronized 锁升级的原理是什么?
  • synchronized锁升级的原理:在锁对象的对象头里有一个threadid字段,在第一次访问的时候threadid为空,jvm让其持有偏向锁,并将threadid设置为其线程id,再次进入的时候会先判断threadid是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized锁的升级。
  • 锁的升级的目的:锁升级是为了减低锁带来的性能消耗。在Java6之后优化了synchronized的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
什么是死锁?
  • 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。
怎么防止死锁?
  • 死锁的四个必要条件:
    1、互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
    2、不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己主动释放。
    3、请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。
    4、循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。即存在一个处于等待状态的线程集合{Pl, P2, …, pn},其中Pi等待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有
  • 避免死锁的方式:
    1、加锁顺序(线程按照一定的顺序加锁)。
    2、加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)。
    3、死锁检测。
ThreadLocal 是什么?有什么使用场景?
  • ThreadLocal 是 JDK java.lang 包中的一个用来实现相同线程数据共享不同的线程数据隔离的一个工具。一句话说就是 ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用(相同线程数据共享),也就是变量在线程间隔离(不同的线程数据隔离)而在方法或类间共享的场景。
说一下 synchronized 底层实现原理?

-synchronized代码块是由一对monitorenter和monitorexit指令实现的,Monitor对象是同步的基本实现单元。现代java虚拟机对sychronized进行了优化,引入了偏斜锁、轻量级锁、重量级锁

synchronized 和 volatile 的区别是什么?
  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的.
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性.
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
synchronized 和 Lock 有什么区别?
  • 存在层次:synchronized是java的关键字,在jvm层面上。而lock是一个类。
  • 锁的释放:以获取锁的线程执行完同步代码,释放锁。线程执行发生异常,jvm会让线程释放锁。而lock在finally中必须释放锁,不然容易造成线程死锁。
  • 锁的获取:假设A线程获得锁,B线程等待,如果A线程阻塞,B线程会一直等待。而分情况而定,lock有多个锁获取的方法,可以尝试获得锁,线程可以不用功一直等待。
  • 锁的状态:synchronized无法判断。而lock可以判断。
  • 锁的类型:synchronized可以重入,不可以中断,非公平。而lock可重入 可以判断 可公平。
  • 性能:synchronized少量同步。而lock大量同步。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值