1. 多线程的创建方式?

  1. 继承Thread类重写Run方法
  2. 实现Runnable接口
  3. 实现Callable接口通过FutureTask包装器来创建Thread线程(有返回值)
  4. 线程池

2. 简述线程、进程、程序的基本概念?

进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位

线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务

程序:是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码

3. start 和 run 方法有什么区别?

调用start方法方可启动线程,而run方法只是thread类中的一个普通方法调用,还是在主线程里执行

4. 可以直接调用Thread类的run()方法吗?

可以直接调用,但是只是执行了一个普通的run方法而已,并没有真正的启动线程

5. 多线程的好处?

多线程的好处提高程序的效率。
充分发挥多核计算机的优势 4 8 线程

6. 多线程应用场景?(结合项目去说TODO)

image.png

7. 用户线程和守护线程

Java分为两种线程:用户线程和守护线程

所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
设置守护线程:(垃圾回收)
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断、

8. 线程的优先级

线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们通过提高线程的优先级来最大程度的改善线程获取时间片的几率。

线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:
Thread.MIN_PRIORITY 1
Thread.MAX_PRIORITY 10
Thread.NORM_PRIORITY 5
void setPriority(int priority):设置线程的优先级。
t1.setPriority(10)、

9. 如何停止一个正在运行的线程

image.png
(1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。(运行结束后自动的结束)
(2)使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。(强制退出抛出异常:影响代码的一致性)
(3)使用interrupt方法中断线程(把当前的中断状态改为true,默认是false,调用isInterrupt判断)

10. 如何保证3个线程依次执行 t1>t2>t3

控制线程执行;插队运行
image.png
image.png

11. 线程的生命周期:

image.png
image.png

12. JMM(内存模型)

Java内存模型(即Java Memory Model,简称JMM)。
JMM本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据。而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。
线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
image.png

13. synchroniczed底层原理

Jvm 底层调用了一个对象监视器Monitor实现的锁的监控
image.png
14.一定要思考谁才是锁对象
image.png

14. Jdk1.6以后的synchroniczed锁的升级

根据并发量的不同,设置锁的级别:
image.png

15. 什么是死锁;

image.png
同时开始的俩个不同的线程,在得到锁后,互相需要对方的锁才能继续执行,但是锁又在对方使用中不能释放

16. 如何解决死锁(JVM TODO:)

Jstack命令
image.png
不使用嵌套就能避免死锁的问题

17. 1.5之后 跟synchroniczed的区别

image.png
lock锁还提供了一个读写锁

18. 线程的三种特性:

19. CAS — JAVA乐观锁实现

image.png
ABA问题:
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

看到这 有没有感觉和我们在数据库中通过version实现乐观锁比较像呢~~ CAS是通过java中 unsafe类中的代码实现, native boolean compareAndSwapInt

可以看到 方法是由native修饰的,也是通过底层C++代码实现的。 并且unsafe中的方法中有安全限制,我们是无法直接调用的。 我可以通过调用封装好的一些原子类
如: AtomicInteger int类型的原子类, 提供int的修改原子操作。
image.png
注意:image.png
CAS比加锁的效果更好一些,但是并发量不能太大(浪费资源,内存泄漏)

20. 多线程

  1. 核心参数:

image.png

  1. 多线程原理:

image.png

提交一个任务到线程池中,线程池的处理流程如下:
1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

  1. 关闭线程池

image.png
立刻关闭,和等待任务完毕关闭

  1. 线程池有几种:(都是工具类,底层都是TreadPoolExcutor)

image.png

21. wait跟sleep的区别

wait需要notify唤醒执行,sleep时间结束执行
wait会释放锁,sleep不会