• java多线程高并发面试题


    1. 什么是进程?线程?区别?

    1)进程是一个独立的运行环境,它可以被看作是一个程序或者一个应用。而线程是在进程中执行的一个任务。eg:打开360安全卫士,它本身是一个程序,也是一个进程,它里面有杀毒,清理垃圾,电脑加速等功能,当你点击杀毒的时候,杀毒任务就相当于一个线程。

    2)进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。

    3)进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能。

    2. 什么叫线程安全?

      一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

    出现线程不安全的原因是什么?

           如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题。

    线程不安全解决办法?

      要求一个线程操作共享数据时,只有当其完成操作共享数据,其它线程才有机会执行共享数据。java提供了两种方式来实现同步互斥访问:synchronized和Lock。

    3. 创建线程的方式?区别?

    1)继承Thread类;

    2)实现Runnable接口;

    3)实现Callable接口;

    4)  通过线程池创建线程;

    区别

    Java中,类仅支持单继承,如果一个类继承了Thread类,就无法再继承其它类,因此,如果一个类既要继承其它的类,又必须创建为一个线程,就可以使用实现Runable接口的方式。

    使用实现Runable接口的方式创建的线程可以处理同一资源,实现资源的共享。

    使用实现Callable接口的方式创建的线程,可以获取到线程执行的返回值、是否执行完成等信息。

    4.  Java中Runnable和Callable有什么不同?

    Runnable和Callable都是创建线程的方式。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。

    (1)实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

    (2)Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

    5. Thread 类中的start() 和run() 方法有什么区别?

    介绍说明

    (1) start() :它的作用是启动一个新线程。通过start()方法来启动的新线程,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。

     (2) run():就和普通的成员方法一样,可以被重复调用。如果直接调用run方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行。

    start和run区别

     (1) start() 可以启动一个新线程,run()不能。

     (2) start()不能被重复调用,run()可以。

     (3)start()中的run代码可以不执行完就继续执行下面的代码,即进行了线程切换。直接调用run方法必须等待其代码全部执行完才能继续执行下面的代码。

     (4)start() 实现了多线程,run()没有实现多线程。

    6.  Java多线程中调用wait() 和 sleep()方法有什么不同?

    sleep()和wait()都是使线程暂停执行一段时间的方法。二者区别为:

    1)原理不同(面试时可不答,偏重2,3)

    sleep()方法是Thread类的静态方法,是线程用来控制自身流程的。

    而wait()方法是Object类的方法,用于线程间的通信。

    2)对锁的处理机制不同

    调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放锁。

    3)使用地方不同

    sleep方法则可以放在任何地方使用,而wait()方法必须放在同步方法或者同步代码块中使用。

    sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。

    推荐:由于sleep不会释放锁标志,容易导致死锁问题的发生,一般情况下,不推荐使用sleep()方法,而推荐使用wait()方法。

    7.介绍下CAS

    CAS(Compare and Swap),比较与交换,实现并发算法时常用到的一种技术,是Java保证原子性的一种重要方法,也是一种乐观锁的实现方式。CAS有3个参数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

    缺点:ABA问题

    说说对于 synchronized 关键字的了解?

    synchronized关键字解决的是多个线程之间访问资源的同步性;

    synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

    synchronized保证了对变量操作的可见性,原子性和有序性。

    synchronized 的使用方式有三种:

    (1)修饰同步代码块

    (2)修饰非静态(实例)的方法

    (3)修饰静态的方法

    eg;双重校验锁实现单例模式使用到了synchronized

    Lock和synchronized的区别

     (1)synchronized是Java中的关键字,在JVM层面,而Lock是一个接口;

    (2)synchronized会自动释放线程占有的锁,而Lock需要主动通过unLock()去释放锁,否则可能造成死锁现象。

    (3)使用synchronized时,等待的线程会一直等待下去,不能够响应中断,而Lock可以让等待锁的线程响应中断;

    (4)通过Lock可以判断锁状态,即是否成功获取锁,而synchronized无法判断。

    (5)Lock可以提高多个线程进行读操作的效率。

    说明:在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况。

    synchronized和Lock底层实现?

    synchronized用的锁是存在java对象里的,通过对代码反编译,可以看出被synchronized修饰的代码块,在执行之前先使用monitorenter指令加锁,然后在执行结束之后再使用monitorexit指令释放锁资源,在整个执行期间此代码都是锁定的状态,这就是典型悲观锁的实现流程。

    lock锁使用的是CAS和volatile来实现同步的,CAS使用硬件命令实现缓存一致性保证了原子性,volatile保证了可见性,多线程环境下所有的线程通过CAS进行竞争资源,只能有一个成功,其它的都会自旋。

     在多线程中,什么是上下文切换?

    上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。是多任务操作系统和多线程环境的基本特征。 

    并发编程三要素

    (1)   原子性:程序中的所有操作是不可中断的,要么全部执行成功要么全部执行失败。

    (2) 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

    (3) 可见性:当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。

    Java中堆和栈有什么不同?(相对于线程来说)

      栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。

      堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

     什么是线程池? 为什么要使用它?

      线程池(thread pool):一种线程使用模式。

      创建线程要花费资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

    线程池的好处:

    (1)通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。

    (2)对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。

    什么是ThreadLocal?

      ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程共享它的全局变量,所以这些变量是非线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。每个线程都会拥有他们自己的Thread变量,它们可以使用get()/set()方法去获取他们的默认值或者在线程内部改变他们的值。

    死锁是什么?如何避免死锁?

      死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

    1)互斥条件:一个资源每次只能被一个进程使用。

    2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

    3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

    4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

      避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

    Thread类中的yield方法有什么作用?

      Thread.yield() 方法会使当前线程从运行状态变为就绪状态,把运行机会让给其它相同优先级的线程。它是一个静态的原生(native)方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能会被再次继续执行的。

    Java中notify 和 notifyAll有什么区别?

      调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒。

     Java中interrupted 和 isInterruptedd方法的区别?

      interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。

      Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

      有三个线程T1,T2,T3,怎么确保它们按顺序执行?

    在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

     如何创建守护线程?

      使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。

     
    /**
     * @author liao.wenhui
     * @date 2019/7/15 15:13
     */
    public class DaemonThread {
        public static void main(String[] args) {
            Thread daemonThread = new Thread(new Runnable() {
                @Override
                public void run() {
    
                }
            });
    
            //设置守护线程
            daemonThread.setDaemon(true);
            daemonThread.start();
        }
    }
     

    前提知识:

      守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件(百度百科)。

      Java线程分为两类分别为daemon线程(守护线程)和User线程(用户线程),在JVM启动时候会调用main函数,main函数所在的线程是一个用户线程,这个是我们可以看到的线程,其实JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别那?区别之一是当最后一个非守护线程结束时候,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意是只要有一个用户线程还没结束正常情况下JVM就不会退出。

    什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

      线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。

      时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(即最好不要让你的程序依赖于线程的优先级)。

    Java线程池中submit() 和 execute()方法有什么区别?

      两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

    什么是FutureTask?

      在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

  • 相关阅读:
    1226 倒水问题
    1230 元素查找
    2152 滑雪
    1099 字串变换 2002年NOIP全国联赛提高组
    3027 线段覆盖 2
    P2066 机器分配
    spring的作用及优势---第一个spring示例
    密码框显示提示文字
    紫薇~还记得大明湖畔的HTML5智力拼图吗?
    细说javascript函数
  • 原文地址:https://www.cnblogs.com/sx66/p/12623437.html
Copyright © 2020-2023  润新知