0. 思维导图
并行:单位时间多个处理器同时处理多个任务
并发:一个处理器处理多个任务,按时间片轮流处理
1. java实现多线程有几种方式♥♥♥
实现接口会更好一些,因为java不支持多重继承,因此继承了Thread类就无法继承其他类,但是可以实现多个接口
继承Thread类
,只需要创建一个类继承Thread类然后重写run方法,在main方法中调用该类实例对象的start方法。实现Runnable接口
,只需要创建一个类实现Runnable接口然后重写run方法,在main方法中将该类的实例对象传给Thread类的构造方法,然后调用start方法。实现Callable接口
,只需要创建一个类实现Callable接口然后重写call方法(有返回值),在main方法中将该类的实例对象传给Future接口的实现类FutureTask的构造方法,然后再将返回的对象传给Thread类的构造方法,最后调用start方法。线程池
,首先介绍它的好处,然后再说它可以通过ThreadPoolExecutor类的构造方法来进行创建。
补充:为什么不能直接调用run方法
- 调用start方法方可启动线程并使线程进入就绪状态,直接执行run方法的话不会以多线程的方式执行。
补充:多线程的优缺点?
- 优点:当一个线程进入阻塞或等待状态,cpu可以先去执行其他线程,
提高cpu利用率
- 缺点:
频繁的上下文切换
会影响多线程的执行速度- 容易死锁
2. 线程池相关内容♥♥♥
-
好处:
- 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度,当任务到达时,任务可以不需要等待线程就能立即执行
- 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅消耗资源而且降低系统的稳定性,使用线程池可以进行线程的统一分配,调优和监控。
-
Executor框架的主要成员:ThreadPoolExecutor、ScheduledThreadPoolExecutor、future接口、Runnable接口、Callable接口和Executors
-
创建线程池的两种方式:
通过ThreadPoolExecutor构造函数
(推荐)- 通过Executor框架的工具类Executors来实现。我们现在创建三种类型的ThreadPoolExecutor:(不推荐,容量为Integer.MAX_VALUE,所以容易OOM)
- CachedThreadPool:返回一个可根据实际情况调整线程数量的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- FixedThreadPool:返回一个固定线程数量的线程池,可控制线程最大并发数,超过的线程会在队列中等待。
- SingleThreadExecutor:返回一个只有一个线程的线程池,它只会用唯一的线程来执行任务,保证所有任务按照执行顺序执行。
-
ThreadPoolExecutor构造方法的7个参数:
-
corePoolsize:线程池的核心线程数(最小可以同时运行的线程数)
-
maximumPoolSize:线程池的最大线程数(当任务队列存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数)
-
keepAliveTime:当线程数大于核心线程数时,多余的空闲线程存活的最长时间(如果此时没有新的任务提交,核心线程外的线程不会立即销毁,而是等到这么长时间,会被回收销毁)
-
unit:时间单位
-
workQueue:任务队列,用来存储等待执行任务的队列(当新的任务来的时候,会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,就会被放入队列中)
-
ThreadFactory:线程工厂,用来创建线程
-
RejectedExecutionHandler:拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
- abortPolicy:抛出RejectedExecutionException来拒绝新任务的处理(默认)
- CallerRunsPolicy:用调用者的线程运行任务
- DiscardPolicy:不处理新任务,直接丢弃掉
- DiscardOldestPolicy:丢弃最早的未处理的任务请求
-
ScheduledThreadPoolExecutor:用来在给定的延迟后运行任务,或者定期执行任务(一般不会使用)
-
3. 线程有哪几种状态♥♥
-
线程一共有6种状态,分别是
NEW新建、Runnable运行、Blocked阻塞、限期等待、无期限等待、Terminated终止
- 新建状态表示 线程被创建但是还没有启动
- 运行状态表示 线程有可能正在执行,也有可能在等待CPU分配资源
- 阻塞状态表示
线程没有获取到monitor锁
- 限期等待状态表示在一定时间后会有系统自动唤醒【线程在run方法的内部调用了wait()、join()或者sleep(),并且传入了等待时间参数】
- 无限期等待状态表示 不会分配CPU资源,需要显示唤醒【线程在run方法的内部调用了wait()或者join()】
- 终止状态 表示 线程执行结束
-
线程中阻塞和等待的区别
- 阻塞状态指线程在等待其他线程释放monitor锁,等待状态指线程在等待其他线程执行某些操作,比如wait方法执行完毕或者等待调用notify方法唤醒线程。
4. sleep、wait、notify、yield和join方法的区别♥
多线程下,需要调用这个对象的synchronized方法或synchronized块必须获得对象锁,此时没有获取到线程会进入锁池;而获取到锁的线程如果调用了wait方法,线程就会进入等待池,进入等待池的线程不会竞争该对象的锁。
-
sleep()方法:让当前正在执行的线程在指定的时间内暂停执行
- 和wait()方法最大的区别:sleep没有释放锁,而wait释放了锁;调用sleep后自动唤醒,而wait不会自动唤醒,需要其他线程调用notify方法
-
wait()方法:释放对象锁,进入等待池(
一定是写在synchronized代码块中
) -
notify()方法:从等待池中移出任意一个线程放入锁池中(
一定是写在synchronized代码块中
) -
notifyAll()方法:会将等待池中的所有线程都移动到锁池中(
一定是写在synchronized代码块中
) -
yield()方法:使当前线程重新回到可执行状态(CPU时间片用完了),所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
-
join()方法
:会使当前线程等待调用join()方法的线程结束后才继续执行。
5. 什么是上下文切换♥
单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制
,因为时间片非常短(几十毫秒),所以CPU通过不停的切换线程执行让我们感觉多个线程是同时执行的CPU通过时间分片分配算法来循环执行任务
,当前任务执行完一个时间片后会切换到下一个任务,但是在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态,所以,任务从保存到再加载的过程就是一次上下文切换
6. 设计一个简单的死锁程序♥♥
- 解释:两个线程同时开启,线程1先获得obj1对象的锁,然后想要获得对象obj2的锁,这个时候刚好线程2获得了对象obj2的锁,因此线程1会被阻塞,那么线程2也会想要获取对象obj1的锁,显然是无法得到的,也会进入阻塞状态,这就导致两个线程都无法释放自己的锁而结束。
7. ThreadLocal相关内容♥♥
- 定义:ThreadLocal叫做线程变量,也就是说该变量是当前线程独有的变量。
ThreadLocal为变量在每个线程中都创建了一个副本
,那么每个线程可以访问自己内部的副本变量,因此就不会有多线程安全问题。 - 方法:他们可以使用get和set方法来获取默认值或将其值更改为当前线程所存的副本的值。
- 原理:我们从
Thead类源码中可以看到有一个threadLocals变量,它是ThreadLocalMap类型的变量,而ThreadLocalMap是ThreadLocal的静态内部类,当我们调用set或者get方法的时候,实际上是将变量存放到了当前线程的ThreadLocalMap
- 问题:
内存泄漏
;ThreadLocalMap中使用的key为ThreadLocal的弱引用,而value是强引用,所以在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产生内存泄漏。解决手段:手动调用remove()方法
。
8. 并发编程的三个问题♥♥
- 可见性:是指一个线程对共享变量进行修改,另一个线程要立即得到修改后的最新值。
- 原子性:是指在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行
- 有序性:是指程序中代码的执行顺序,java在编译和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是编写代码时的顺序(指令重排在单线程下可以提高性能,但在多线程下会出现问题)
9. 介绍一下java内存模型♥♥♥
- java内存模型是一套规范,描述了java程序中各种变量(线程共享变量)的访问规则,
其规则所有的共享变量都存储在主内存中,每一个线程都有自己的工作内存,工作内存中只存储该线程对共享变量的副本
,线程对变量的所有操作都必须在工作内存中完成,而不能直接读写主内存的变量。 - 作用:在多线程读写共享数据时,对共享数据的可见性、有序性、和原子性的保证。
- 主内存和工作内存之间的数据交互过程:lock->read->load->use->assign->store->write->unlock
- 注意:
- 如果对一个变量执行lock操作,将会清除工作内存中此变量的值。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中
10. synchronized是如何保证三大特性♥♥
11. synchronized的特性♥♥
12. synchronized 的原理♥♥♥
13. ReentrantLock底层原理♥♥♥
14. synchronized与lock的区别♥♥♥
15. volatile的作用♥♥
16. volatile和synchronized的区别♥♥♥
17. CAS介绍一下♥♥
18. 乐观锁和悲观锁的区别♥♥
19. 锁升级的过程♥