二十一、说说你对线程安全的理解
与其说是线程安全,不如说是内存安全,堆是共享内存,可以被所有线程访问。
堆是进程和线程共有的空间,每一个进程里面有多个线程,分全局堆和局部堆,全局堆就是所有没有分配的空间,局部堆就是分配给用户的空间,堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了还要还给操作系统,要不然就是内存泄漏。
栈 是每个线程独有的,所以栈是线程安全的,每个线程的栈互相独立。
目前主流的操作系统都是多任务的,即多个进程同事运行,为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存),进程内的所有线程都可以访问这个区域,所以这个区域是线程不安全的。
二十二、Thread和Runable的区别
Thread和Runnable的实质是继承关系,没有可比性,无论使用Runnable还是Thread,都会new Thread,然后执行run()方法,用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
二十三、说说你对守护线程的理解
守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是在整个JVM中所有非守护线程的保姆。
守护线程类似于整个进程的一个小兵,它的生死无关重要,但是它却依赖整个进行而运行,如果其他线程结束了,没有要执行的了,程序就结束了,守护线程立马就中断了。
注意:由于守护线程的终止是自身无法控制的,因此不要把IO、File等重要操作逻辑分配给它,因为它不靠谱。
二十四、ThreadLocal的原理的使用场景
每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocal,它存储本线程中所有ThreadLocal对象及其对应的值。
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,获取对应的value。
使用场景:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离。
3、进行实物操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
二十五、ThreadLocal内存泄漏原因,如何避免
内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重。
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。
强引用:使用最普通的引用 new,一个对象具有强引用,不会被垃圾回收器回收,当内存空间不足,java虚拟机宁愿抛出outOfMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消强引用和某个对象之间的关联,可以显式地将引用复制为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用:jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象,在java中,用java.lang.ref.WeakReference类来表示,可以在缓存中使用弱引用。
ThreadLocal的实现原理,每一个Thread维护了一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。
ThreadLocal正确的使用方法:
每次使用完ThreadLocal都调用它的remove()方法清除数据。
二十六、并发、并行、串行的区别
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着。
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
并发允许两个任务彼此干扰,统一时间点,只有一个任务运行,交替执行。
二十七、并发的三大特性
1、原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行,就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。
2、可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程可以立刻看到这个新变化的值。
3、有序性
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码顺序来执行,有可能将他们重排序,实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
二十八、为什么要用线程池?解释下线程池参数
1、降低资源消耗:提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度:任务来了,直接有现成可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性:线程是稀缺资源,使用线程池可以统一分配调优监控。
1》 corePoolSize代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程。
2》maxinumPoolSize代表最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务比较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数。
3》keepAliveTime、unit表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超过核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过setkeepAliveTime来设置空闲时间。
4》workQueue用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还在持续进入则会开始创建新的线程。
5》ThreadFactory实际上是一个线程工厂,用来生产线程执行任务。
6》Handler:任务拒绝策略,当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,就执行任务拒绝策略。
二十九、简述线程池处理流程
三十、线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程
1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
2、阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
3、阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu的资源。
4、在创建新线程的时候,要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率,所以说线程的创建是比较消耗资源的。