- 1.进程和线程之间的关系?
- 2.程序计数器为什么是私有的?
- 3.虚拟机栈和本地⽅法栈为什么是私有的?
- 4. ⼀句话简单了解堆和⽅法区
- 5. 使⽤多线程可能带来什么问题?
- 6.说说线程的⽣命周期和状态?
- 7.上下文切换?
- 8. 什么是线程死锁?如何避免死锁?
- 9.说说 sleep() ⽅法和 wait() ⽅法区别和共同点?
- 10.为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我
- 们不能直接调⽤ run() ⽅法?
- 11. 说⼀说⾃⼰对于 synchronized 关键字的了解
- 12. 说说⾃⼰是怎么使⽤ synchronized 关键字
- 13.双重校验锁实现对象单例(线程安全)
- 14. 构造⽅法可以使⽤ synchronized 关键字修饰么?
- 15.为什么要弄⼀个 CPU ⾼速缓存呢?
- 16.说说 synchronized 关键字和 volatile 关键字的区别
- 17.ThreadLocal 了解么?
- 18. ThreadLocal 内存泄露问题了解不?
- 19. 为什么要⽤线程池?
- 20. 实现 Runnable 接⼝和 Callable 接⼝的区别
- 21. 执⾏ execute()⽅法和 submit()⽅法的区别是什么呢?
- 22.ThreadPoolExecutor 构造函数重要参数分析
- 23.线程池原理分析
- 24.Atomic 原⼦类
- 25.线程的生命周期
1.进程和线程之间的关系?
2.程序计数器为什么是私有的?
3.虚拟机栈和本地⽅法栈为什么是私有的?
虚拟机栈:
每个 **Java ⽅法**在执⾏的同时会创建⼀个**栈帧**⽤于存储局部变量表、操作数栈、常量池引⽤等信息。从⽅法调⽤直⾄执⾏完成的过程,就**对应着⼀个栈帧在 Java 虚拟机栈中⼊栈和出栈的过程**。
本地⽅法栈:
和虚拟机栈所发挥的作⽤⾮常相似,区别是: 虚拟机栈为虚拟机执⾏ Java⽅法 (也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。 在HotSpot 虚拟机中和 Java 虚拟机栈合⼆为⼀。
所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地⽅法栈是线程私有的。
4. ⼀句话简单了解堆和⽅法区
堆和⽅法区是所有线程共享的资源,其中堆是进程中最⼤的⼀块内存,主要⽤于存放新创建的对象 (所有对象都在这⾥分配内存),⽅法区主要⽤于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
5. 使⽤多线程可能带来什么问题?
并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、死锁 。
6.说说线程的⽣命周期和状态?
7.上下文切换?
当前任务在执⾏完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是⼀次上下⽂切换
8. 什么是线程死锁?如何避免死锁?
线程死锁描述的是这样⼀种情况:多个线程同时被阻塞,它们中的⼀个或者全部都在等待某个资
源被释放。由于线程被⽆限期地阻塞,因此程序不可能正常终⽌。
避免死锁?
- 破坏互斥条件 :这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的(临界资源需要互斥访问)。
2. 破坏请求与保持条件 :⼀次性申请所有的资源。
3. 破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
4. 破坏循环等待条件 :靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。破坏循环等待条件。9.说说 sleep() ⽅法和 wait() ⽅法区别和共同点?
两者最主要的区别在于: sleep() ⽅法没有释放锁,⽽ wait() ⽅法释放了锁 。
两者都可以暂停线程的执⾏。
wait() 通常被⽤于线程间交互/通信, sleep() 通常被⽤于暂停执⾏。
对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于
Object 类中的
wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或notifyAll() ⽅法。 sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(longtimeout) 超时后线程会⾃动苏醒。
10.为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我
们不能直接调⽤ run() ⽅法?
new ⼀个 Thread,线程进⼊了新建状态。调⽤ start() ⽅法,会启动⼀个线程并使线程进⼊了就绪状态,当分配到时间⽚后就可以开始运⾏了。 start() 会执⾏线程的相应准备⼯作,然后⾃动执⾏ run() ⽅法的内容,这是真正的多线程⼯作。 但是,直接执⾏ run() ⽅法,会把 run()⽅法当成⼀个 main 线程下的普通⽅法去执⾏,并不会在某个线程中执⾏它,所以这并不是多线程⼯作。
总结: 调⽤ start() ⽅法⽅可启动线程并使线程进⼊就绪状态,直接执⾏ run() ⽅法的话不会
以多线程的⽅式执⾏
11. 说⼀说⾃⼰对于 synchronized 关键字的了解
synchronized 关键字解决的是多个线程之间访问资源的同步性, synchronized 关键字可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。
另外,在 Java 早期版本中synchronized 属于 重量级锁,效率低下因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原⽣线程之上的
12. 说说⾃⼰是怎么使⽤ synchronized 关键字
1.修饰实例⽅法:** 作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
2.修饰静态⽅法: 也就是给当前类加锁,会作⽤于类的所有对象实例 ,进⼊同步代码前要获得 当 前 class 的锁。因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个静态资源,不管 new 了多少个对象,只有⼀份)。所以,如果⼀个线程 A 调⽤⼀个实例对象的⾮静态 synchronized ⽅法,⽽线程 B 需要调⽤这个实例对象所属类的静态 synchronized ⽅法,是允许的,不会发⽣互斥现象,因为访问静态 synchronized ⽅法占⽤的锁是当前类的锁,⽽访问⾮静态synchronized ⽅法占⽤的锁是当前实例对象锁。
3.修饰代码块 :指定加锁对象,对给定对象/类加锁**。 synchronized(this|object) 表示进⼊同步代码库前要获得给定对象的锁。 synchronized(类.class) 表示进⼊同步代码前要获得当前 class 的锁
总结:
synchronized 关键字加到 static 静态⽅法和 synchronized(class) 代码块上都是是给 Class类上锁,synchronized 关键字加到实例⽅法上是给对象实例上锁。尽量不要使⽤ synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能!
13.双重校验锁实现对象单例(线程安全)
14. 构造⽅法可以使⽤ synchronized 关键字修饰么?
先说结论:构造⽅法不能使⽤ synchronized 关键字修饰。
构造⽅法本身就属于线程安全的,不存在同步的构造⽅法⼀说。
15.为什么要弄⼀个 CPU ⾼速缓存呢?
CPU Cache 缓存的是内存数据⽤于解决 CPU 处理速度和内存不匹配的问题,内存缓存的是硬盘数据⽤于解决硬盘访问速度过慢的问题
16.说说 synchronized 关键字和 volatile 关键字的区别
synchronized 关键字和 volatile 关键字是两个互补的存在,⽽不是对⽴的存在!volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定⽐ synchronized 关键字要好。但是 volatile 关键字只能⽤于变量⽽ synchronized 关键字可以修饰⽅法以及代码块。volatile 关键字能保证数据的可⻅性,但不能保证数据的原⼦性。 synchronized 关键字两者都能保证。volatile 关键字主要⽤于解决变量在多个线程之间的可⻅性,⽽ synchronized 关键字解决的是多个线程之间访问资源的同步性。
17.ThreadLocal 了解么?
通常情况下,我们创建的变量是可以被任何⼀个线程访问并修改的。如果想实现每⼀个线程都有⾃⼰的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问 ThreadLocal 类主要解决的就是让每个线程绑定⾃⼰的值,可以将 ThreadLocal 类形象的⽐喻成存放数据的盒⼦,盒⼦中可以存储每个线程的私有数据。
18. ThreadLocal 内存泄露问题了解不?
ThreadLocalMap 中使⽤的 key 为 ThreadLocal 的弱引⽤,⽽ value 是强引⽤。所以,如果ThreadLocal 没有被外部强引⽤的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会被清理掉。这样⼀来, ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远⽆法被 GC 回收,这个时候就可能会产⽣内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调⽤ set() 、 get() 、 remove() ⽅法的时候,会清理掉 key 为 null的记录。使⽤完 ThreadLocal ⽅法后 最好⼿动调⽤ remove() ⽅法
19. 为什么要⽤线程池?
使⽤线程池的好处:
降低资源消耗。通过重复利⽤已创建的线程降低线程创建和销毁造成的消耗。
提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
提⾼线程的可管理性。线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降
低系统的稳定性,使⽤线程池可以进⾏统⼀的分配,调优和监控。
20. 实现 Runnable 接⼝和 Callable 接⼝的区别
Runnable ⾃ Java 1.0 以来⼀直存在,但 Callable 仅在 Java 1.5 中引⼊,⽬的就是为了来处理 Runnable 不⽀持的⽤例。 Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝可以。所以,如果任务不需要返回结果或抛出异常推荐使⽤ Runnable 接⼝,这样代码看起来会更加简洁。⼯具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
21. 执⾏ execute()⽅法和 submit()⽅法的区别是什么呢?
- execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否;
2. submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执⾏成功,并且可以通过 Future 的 get() ⽅法来获取返回值, get() ⽅法会阻塞当前线程直到任务完成,⽽使⽤ get(long timeout,TimeUnit unit)⽅法则会阻塞当前线程⼀段时间后⽴即返回,这时候有可能任务没有执⾏完。22.ThreadPoolExecutor 构造函数重要参数分析
ThreadPoolExecutor 3 个最重要的参数:
corePoolSize : 核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。
maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数
量变为最⼤线程数。
workQueue : 当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达
到的话,新任务就会被存放在队列中。ThreadPoolExecutor 饱和策略
ThreadPoolExecutor 饱和策略定义:
如果当前同时运⾏的线程数量达到最⼤线程数量并且队列也已经被放满了任时ThreadPoolTaskExecutor 定义⼀些策略:
ThreadPoolExecutor.AbortPolicy :抛出RejectedExecutionException 来拒绝新任务的处理。 ThreadPoolExecutor.CallerRunsPolicy :调⽤执⾏⾃⼰的线程运⾏任务.但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应⽤程序可以承受此延迟并且你不能任务丢弃任何⼀个任务请求的话,你可以选择这个策略。
ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。23.线程池原理分析
24.Atomic 原⼦类
原⼦类说简单点就是具有原⼦/原⼦操作特征的类基本类型
使⽤原⼦的⽅式更新基本类型
AtomicInteger :整形原⼦类
AtomicLong :⻓整型原⼦类
AtomicBoolean :布尔型原⼦类数组类型
使⽤原⼦的⽅式更新数组⾥的某个元素
AtomicIntegerArray :整形数组原⼦类
AtomicLongArray :⻓整形数组原⼦类
AtomicReferenceArray :引⽤类型数组原⼦类引⽤类型
AtomicReference :引⽤类型原⼦类
AtomicStampedReference :原⼦更新带有版本号的引⽤类型。该类将整数值与引⽤关联起
来,可⽤于解决原⼦的更新数据和数据的版本号,可以解决使⽤ CAS 进⾏原⼦更新时可能
出现的 ABA 问题。
AtomicMarkableReference :原⼦更新带有标记位的引⽤类型对象的属性修改类型
AtomicIntegerFieldUpdater :原⼦更新整形字段的更新器
AtomicLongFieldUpdater :原⼦更新⻓整形字段的更新器
AtomicReferenceFieldUpdater :原⼦更新引⽤类型字段的更新器
25.线程的生命周期
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态