Java工程师面试1000题71-80

71、什么是线程安全和线程不安全?
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护隔离,使其他线程不能进行访问,直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成得到的数据是脏数据,如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说这个全局变量就是线程安全的,若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

72、什么是Java内存模型?
Java内存模型描述了在多线程代码中哪些行为是合法的,以及线程如何通过内存进行交互。他描述了“程序中的变量”和“从内存或者寄存器获取和存储它们的底层细节”之间的关系。Java内存模型通过使用各种各样的硬件和编译器的优化来正确实现以上事情。

Java包含了几个语言级别的关键字,包括:volatile、final和synchronized,目的是为了帮助程序员向编译器描述一个程序的并发需求。Java内存模型定义了volatile和synchronized的行为,更重要的是保证了同步的Java程序在所有的处理器架构下面都能正确的运行。

“一个线程的写操作对其他线程可见”这个问题是因为编译器对代码进行重排序导致的。例如,只要代码移动不会改变程序的语义,当编译器认为程序中移动一个写操作到后面会更有效的时候,编译器就会对代码进行移动。如果编译器推迟执行一个操作,其他线程可能在这个操作执行完之前都不会看到该操作的结果,这反映了缓存的影响。

此外,写入内存的操作能够被移动到程序里更前的时候。在这种情况下,其他的线程在程序中可能看到一个比它实际发生更早的写操作。所有的这些灵活性的设计是为了通过给编译器,运行时或硬件灵活性使其能在最佳顺序的情况下来执行操作。在内存模型的限定之内,我们能够获取到更高的性能。

73、什么是JVM内存结构?
JVM内存结构主要有三大块:堆内存、方法区和栈。

堆内存是JVM中最大的一块,由年轻代和老年代组成的,而年轻代内存又被分为三部分:Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配这三部分;方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为了与Java堆区分,方法区还有一个别名——Non-Heap(非堆);栈又分为Java虚拟机栈和本地方法栈,主要用于方法的执行。

74、介绍一下Java堆(Java Heap)。
Java堆是被所有线程共享的,是Java虚拟机所管理的内存中最大的一块区域,Java堆在虚拟机启动的时候就被创建。

Java堆的唯一目的就是用来存放对象实例,几乎所有的对象实例和数组都在这里。Java堆为了便于更好的回收和分配内存,可以细分为:新生代和老年代;再细致一点新生代又分为:Eden空间、From Survivor空间、To Survivor区,系统默认比例是8:1:1。在经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中去,因此可以认为年老代中存放的都是一些生命周期比较长的对象。Survivor空间可以在物理上不连续的内存空间中,只要是逻辑上是连续的即可。Java堆可以通过参数 -Xms和-Xmx设置。

75、介绍一下Java栈(Java stack)。
Java虚拟机栈:

Java虚拟机栈是线程私有的,他的生命周期与线程相同;每一个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。虚拟机栈是执行Java方法的内存模型(也就是字节码)服务;每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

局部变量表:32位变量槽,存放了编译期可知的类型,如基本数据类型、对象引用、returnAddress类型。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的。

操作数栈:基于栈的执行引擎,虚拟机把操作数栈作为它的工作区,大多数指令都要从这里弹出数据、执行运算,然后把结果压回操作数栈。

动态链接:每个栈帧都包含一个指向运行时常量池(方法区的一部分)中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态连接。class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另一部分将在每一次的运行期间转化为直接应用,这部分称为动态连接。

方法出口:返回方法被调用的位置,恢复上层方法的局部变量和操作数栈,如果无返回值,则把它压入调用者的操作数栈。

本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

76、介绍一下方法区(Method Area)。
线程共享内存区域,用于储存已被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码,方法区也称为持久代(Permanent Generation),虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是他却又一个别名叫做Non-Heap,目的是与Java堆区分开来。如何实现方法区,属于虚拟机的实现细节,不受虚拟机规范约束。方法区主要存放Java类定义信息,与垃圾回收关系不大,方法区可以选择不实现垃圾回收,但不是没有垃圾回收。运行时常量池也是方法区的一部分,虚拟机加载class文件后把常量池中的数据放入运行时常量池。

77、什么是乐观锁和悲观锁?
悲观锁:Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁其实就是一种悲观锁,所以可以说synchronized是悲观锁。

乐观锁:乐观锁( Optimistic Locking)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

78、什么是Executors框架?
Executor框架同java.util.concurrent.Executor接口在Java5中被引用,Executors框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池。

Java通过Executors提供四种线程池,分别为:

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

79、什么是阻塞队列?
阻塞队列是一个在队列的基础上又支持两个附加操作的队列,这两个附加操作是:支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满;支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空。

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。简而言之,阻塞队列是生产者用来存放元素、消费者获取元素的容器。

80、说说Java里的阻塞队列。
JDK 7 提供了7个阻塞队列,如下:

1、ArrayBlockingQueue :数组结构组成的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。

2、LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,此队列按照先出先进的原则对元素进行排序。

3、PriorityBlockingQueue:支持优先级的无界阻塞队列。

4、DelayQueue:支持延时获取元素的无界阻塞队列,即可以指定多久才能从队列中获取当前元素

5、SynchronousQueue:不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列。

6、LinkedTransferQueue:由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法

transfer方法:如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点,并等到该元素被消费者消费了才返回。

tryTransfer方法:用来试探生产者传入的元素能否直接传给消费者。,如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。

7、LinkedBlockingDeque:链表结构的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。

猜你喜欢

转载自blog.csdn.net/Mr_Quinn/article/details/88770622