Java多线程面试题

 

 

    1、启动一个线程是调用run()方法还是start()方法?

    启动一个线程是调用start()方法,是线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,这并不意味着线程就会立即执行

  2、请说出同步线程及线程调度相关的方法?

    wait():是一个线程等待(阻塞bolcked)状态,并且释放所持有的对象的锁

    sleep():是一个正在运行状态的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;

    notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关。

    notifyAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得了锁的线程才能进入就绪状态。

    注意:Java5通过Lock接口提供了显式的锁机制,Lock接口中定义了加锁(lock()方法)和解锁(unlock()方法),增强了多线程编程的灵活性及对线程的协调。???

  3、线程和进程的区别?

    进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。

    线程:是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。

    特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间内存共享,这是多线程编程可以拥有更好的相能和用户体验。

    注意:多线程对于其它程序是不友好的,占据大量CPU资源(共享内存的原因?)

  4、多线程的创建方式?

    1):继承Thread类:但Thread本质上也是实现了Runnable接口的一个实例,他代表了一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法(本地方法),它将启动一个新线程,并执行run()方法。这种方法实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:继承Thread类实现多线程,并在适合的地方启动线程。

 

    2):实现Runnable接口的方式实现多线程,并且实例化Thread,传入自己的Thread实例,调用run()方法

    

    3):使用ExecutorService、Callable、Future实现有返回结果的多线程:ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。

      返回结果的线程实在JDK1.5引入;

      可返回值的任务必须实现Callable接口,无返回值的任务必须实现Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get()方法就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。

  5、在Java中wait和sleep方法的不同?

    最大的不同是在等待wait会是释放锁,而sleep一直持有锁。wait通常被用于线程间交互,sleep通常被用于暂停执行。

  6、synchronized和volatile关键字的区别?

    一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

    1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说也是立即可见的。

    2)禁止进行指令重排序

      volatile本质是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取

      synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

      a)volatile仅能使用在变量级别:

         synchronized则可以使用在变量、方法和类级别

      b)volatile仅能实现变量的修改可见性,并不能保证原子性;

         synchronized则可以保证变量的修改可见性和原子性;

      c)volatile不会造成线程的阻塞

        synchronized可能会造成线程阻塞

      d)volatile标记的变量不会被编译器优化

         synchronized标记的变量可以被编译器优化(指令重排序?)

  6、什么是线程池,如何使用?

      线程属于重量级资源,线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高了代码执行效率。

      在JDK的java.util.concurrnet.Executors中提供了生成多种线程池的静态方法。

      一般我们去自己实现Executor接口,自定义线程池。

  7、常用的线程池有哪些?

      newSingleThreadExeCutor:创建一个单线程的线程池,此线程保证所有任务的执行顺序按照任务的提交顺序执行。

      newFixedThreadPool:创建固定大小的线程池,每次提交(submit()方法)一个任务就创建一个线程,直到线程达到线程池的最大大小。

      newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

      newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

  8、对线程池的理解?

      从线程池如何使用、线程池的好处、线程池的启动策略三方面回答

      合理利用线程池的三个好处

      1):减低资源消耗。通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。

      2):提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

      3):提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性(?),使用线程池可以进行同一的分配,调优和监控

      

      线程池的启动策略:

      1):线程池刚刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里有任务,线程池也不会马上执行它们。

      2):当调用execute()方法添加一个任务时,线程池会做如下判断:

        a):如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务

        b):如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。

        c):如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务;

        d):如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会派出异常,告诉调用者“我不能在接收任务了”。

      3):当一个线程完成任务时,它会从队列中取下一个任务来执行。

      4)当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。所以线程池的所有任务完成后,它会最终收缩到corePoolSize的大小。

    

原文地址:https://www.cnblogs.com/scar1et/p/11897426.html