美文网首页
Java面试

Java面试

作者: bigfish1129 | 来源:发表于2018-05-24 17:54 被阅读0次

    Java 面试

    参考文章
    https://blog.csdn.net/sinat_34979383/article/details/60874347

    多线程

    1. sleep与wait的异同点

      • sleep是Thread类的静态方法, wait来自object类
      • sleep方法短暂停顿不释放锁, wait方法条件等待要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
      • wait, notify, notifyall必须在同步代码块中使用, sleep可以在任何地方使用
      • 都可以抛出InterruptedException
    2. 让一个线程停止执行

      • 异常 - 停止执行 休眠 - 停止执行 阻塞 - 停止执行
    3. ThreadLocal简介

      • ThreadLocal解决了变量并发访问的冲突问题
        • 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)
    - 与synchronized同步机制的比较
    
        - 首先,它们都是为了解决多线程中相同变量访问冲突问题。不过,在同步机制中,要通过对象的锁机制保证同一时间只有一个线程访问该变量。该变量是线程共享的, 使用同步机制要求程序缜密地分析什么时候对该变量读写,什么时候需要锁定某个对象, 什么时候释放对象锁等复杂的问题,程序设计编写难度较大, 是一种“以时间换空间”的方式。 而ThreadLocal采用了以“以空间换时间”的方式。
    
    1. 线程局部变量原理

      当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)

      线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

      ThreadLocal的方法:void set(T value)、T get()以及T initialValue()。

      ThreadLocal是如何为每个线程创建变量的副本的:

      首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

      总结:

      实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的

      为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

      在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法

    2. 在多线程环境下,SimpleDateFormat 是线程安全的吗?

      不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。

    3. 线程和进程有什么区别

      从概念上:

      进程:一个程序对一个数据集的动态执行过程,是分配资源的基本单位。

      线程:存在于进程内,是进程内的基本调度单位,共享进程的资源。

      从执行过程中来看:

      进程:拥有独立的内存单元,而多个线程共享内存,从而提高了应用程序的运行效率。

      线程:每一个独立的线程,都有一个程序运行的入口、顺序执行序列、和程序的出口。但是线程不能够独立的执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

      从逻辑角度来看:(重要区别)

      多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但是,操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理及资源分配。

      简言之,一个程序至少有一个进程,一个进程至少有一个线程。进程是资源分配的基本单位,线程共享进程的资源。

    4. 用 Runnable 还是 Thread

      Java 不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是实现Runnable接口好了。

    5. 什么是线程安全?Vector 是一个线程安全类吗

      如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的,而和它相似的 ArrayList 不是线程安全的。

    6. 如何在两个线程间共享数据?

      通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构

    7. Java 中 notify 和 notifyAll 有什么区别

      notify ()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而 notifyAll ()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

    8. 为什么 wait, notify 和 notifyAll 这些方法不在 thread 类里面

      一个很明显的原因是 JAVA 提供的锁是对象级的而不是线程级的。如果线程需要等待某些锁那么调用对象中的 wait ()方法就有意义了。如果 wait ()方法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。

    9. 什么是 FutureTask?

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

    10. Java 中 interrupted 和 isInterruptedd 方法的区别

      interrupted是静态方法,isInterruptedd是一个普通方法

      如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。也就是说isInterrupted 只是简单的查询中断状态,不会对状态进行修改。

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

      创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

    12. 如何写代码来解决生产者消费者问题?

      在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比较低级的办法是用 wait 和 notify 来解决这个问题,比较赞的办法是用 Semaphore 或者 BlockingQueue 来实现生产者消费者模型。

    13. 如何避免死锁?

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

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

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

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

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

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

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

      可以用线程类的 join ()方法。具体操作是在T3的run方法中调用t2.join(),让t2执行完再执行t3;T2的run方法中调用t1.join(),让t1执行完再执行t2。这样就按T1,T2,T3的顺序执行了

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

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

    16. Java 中的 fork join 框架是什么?

      fork join 框架是 JDK7 中出现的一款高效的工具,Java 开发人员可以通过它充分利用现代服务器上的多处理器。它是专门为了那些可以递归划分成许多子模块设计的,目的是将所有可用的处理能力用来提升程序的性能。fork join 框架一个巨大的优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。

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

      Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满足不同的需要。wait ()方法意味着条件等待,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep ()方法仅仅释放 CPU 资源或者让当前线程短暂停顿,但不会释放锁。

    18. 可重入锁

      可重入锁:如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。

    19. 同步方法和同步代码块

      同步方法默认用this或者当前类class对象作为锁; 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法。

    其他参考文章

    java面试参考资料

    相关文章

      网友评论

          本文标题:Java面试

          本文链接:https://www.haomeiwen.com/subject/znnsjftx.html