Java面试题(第五天)
1.对线程安全的理解
不是线程安全,应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象时线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间,堆在操作系统堆进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建,堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例和数组都会在这里分配。
栈是每个线程独有的,保存其运行状态和局部自动变量的,栈在线程开始的时候初始化,每个线程的栈相互独立。因此,栈是线程安全的,操作系统在切换线程的时候会自动切换栈,栈空间不需要再高级语言里面显式的分配和释放
目前主流操作系统都是多任务的,即多个线程同时进行,为了保证安全,每个线程只能访问分配给自己的内存空间,而不能访问别的进程的,这是有操作系统保障的。
在每个进程的内存空间都会有一个特殊的公共区域,通常称为堆(内存),进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。
2.守护线程的理解
守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是整个JVM所有非守护线程的保姆;
守护线程类似于整个进程中的一个默默无闻的小喽啰:它的生死无关重要,它却依赖于整个进程而运行;哪天其他线程技术了,没有要执行的了,程序就结束了吗,理都没理守护线程,就把它中断了;
注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它,因为它不靠谱;
守护线程的作用是什么?
举例,GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有人任何运行的Thread程序就不会再产生垃圾,垃圾回收期就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开,它始终在低级别的状态中运行,用于实时监控和管理体统中的可回收资源。
应用场景:(1)来为其他现场提供服务支持的情况;(2)或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程,通常都是些关键的实物,比方说,数据库录入或者更新,这些操作都是不能中断的。
thread setDaemon(true)必须在thread.start()之间设置,否则会抛出一个IllegalTheadStartException异常,你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
守护线程不能用于去访问固有的资源,比如读写操作或者计算逻辑,因为他会在任何时候甚至一个操作的中间发生中断。
Java自带的多线程框架,比如ExecutorService,会将守护线程转为用户线程,所以如果要使用后台线程就不能用Java的线程池。
3.ThreadLocal的原理和使用场景
每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocal;它储存本线程中所有ThreadLocal对象及其对应的值
ThreadLocalMap由一个个Entry对象构成
Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成,由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没有指向key的强引用后,该key就会被垃圾收集器回收
当执行set方法是,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似,ThreadLocal首先会获取当前线程对象,然后获取当前线程ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocal容器,这些容器互相独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景:
1.在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
2.线程间数据隔离
3.进行事务操作,用于存储线程事务信息
4.数据库连接,Session会话管理。
Spirng框架在事务开始时会给我们当前线程绑定一个JDBC Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用户的ThreadLocal来实现这种隔离。
4.ThreadLocal内存泄漏原因,如何避免
内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏
强引用:使用最普遍的引用(new),一个对象具有很强的引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象,在Java中,用java.lang.ref.WeakReferemce类来表示。可以在缓存中使用弱引用。
ThreadLocal实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例。value为线程变量的副本
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreadLocal)势必会被GC回收,这样就导致ThreadLocalMap中key为null,而value还存在着强引用,只有Thread线程退出以后,value的强引用料条才会断掉,但如果当前
5.并发,并行,串行的区别
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等待
并行在时间上是可重叠的,两个任务在同一时刻互不干扰的同时执行
并发允许两个任务彼此干扰。统一时间点,只有一个任务运行,交替执行。
标签:面试题,Java,对象,ThreadLocal,线程,内存,key,守护 From: https://www.cnblogs.com/qisui/p/17806012.html