个人博客网:https://wushaopei.github.io/ (你想要这里多有)
1、程序、进程、线程的理解
1.1 概念
- 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process)是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
- 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个程序可同一时间执行多个线程,就是支持多线程的
1.2 进程与多线程
每个Java程序都有一个隐含的主线程: main 方法
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
2、多线程的创建和启动
2.1 Thread 线程概念:
2.2 源码流程解析:
(1)Thread 类 与 Runnable接口
并且在Thread中有一个构造器的参数是Runnable的实现子类,所以当一个自定义类实现了Runnable接口后,可以通过newThread()作为构造参数传入来实现Thread多线程的创建。
如图:
(2)Thread 与 Runnable 、Callable 及Future Task 的关系
首先分析:
Future Task的底层实现了RunnableFuture 接口:
而RunnableFuture 的底层实现了两个接口: Runnable , Future
而根据FutureTask 的构造方法和Callable 的类型可知:
由以上可以得知,通过自定义实现了Callable 接口的实例对象做为FutureTask 的构造器形参,然后因为实现了Runnable 接口的缘故,该对象可以做参数传入Thread对象的构造器中,用来创建新的多线程 thread ,并使用 start ()启动线程。
2.3 案例:mt子线程的创建和启动过程
3、线程中常用的API
interrupt() : 中断线程(也会将当前线程中的任务执行完毕会进行线程的一个标记,标记该线程是要被停止。
isInterrupted() :测试此线程是否已被中断。(如果线程的标记被标识成中断则返回true)
3、创建多线程的四中种方式
3.1 继承Thread
- 自定义一个类并继承Thread
- 重写run方法
- run方法中可以写入需要在分线程中执行的代码
- 创建Thread子类的对象
- 通过对象调用start方法
3.2 实现Runnable接口
- 自定义一个类并实现Runnable接口
- 重写run方法
- 在run方法中写入需要在分线程中执行的代码
- 创建Runnable实现类的对象
- 创建Thread对象并将Runnable实现类的对象作为实参传入到Thread的构造器中
- 通过Thread对象调用start方法
3.3 实现Callable接口
- 自定义一个类,并实现Callable接口
- 重写call方法,并返回结果
- 在call方法中实现需要在分线程中执行的代码
- 创建Callable接口的实现类的对象
- 创建FutureTask对象,并将Callable接口的实现类的对象作为实参传到FutureTask的构造器中
- 创建Thread对象,并将FutureTask对象作为实参传入到Thread构造器中
- 调用start方法
代码:
3.4 线程池
4、Thread的生命周期
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
- 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止
5、线程的同步机制
5.1 为什么要使用线程的同步机制?
比如卖票:会发生重票,0票,负票的问题
5.2 解决线程安全问题的方式?
解决方法一 :同步代码块 :
格式 :
1.同步监视器 : 可以是任何对象
注意 : 多个线程必须使用的是同一把锁(必须保证多个线程使用到的同步监视器是同一个对象)。
2.同步代码块内 : 操作共享数据的代码
(实现Runnable使用同步代码块案例 : 详见RunnableTest.java)
解决方法二: 同步方法
格式 :
默认的同步监视器是:this
默认的同步监视是运行时类的对象。 例:Person.class
6、线程的单例模式
7、死锁的问题
死锁的原因:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
代码:
8、线程通信
8.1 线程通信涉及到三个方法:
注意 :
- 上面个方法只能用在同步代码块和同步方法中
- 调用个方法实际上是调用监视对象中的方法
8.2 [面试题] wait和sleep的区别?
- sleep睡觉的时候会抱着锁。wait睡觉的时候会释放锁。
- sleep时间一到就自动唤醒,wait需要被其它线程调用notify/notifyAll才能唤醒。
- sleep是Thread中的方法, wait是Object中的方法。
8.3 [面试题] 继承Thread和实现Runnable的区别?
继承Thread :
同步监视器 : 不可以使用this |
共享资源 : 需要使用static关键字修饰。 |
单继承 |
public static synchronized void say() - 默认锁是运行时类的对象。 比如 : Person.class |
实现Runnable
同步监视器 : 可以使用this |
共享资源 : 不需要使用static关键字修饰。 |
多实现 |
public synchronized void say(){} - 默认锁是this |
9、同步的优点和缺点
- 同步的优点 : 解决了线程安全问题
- 同步的缺点 : 在同步代码块和同步方法的方法体执行时,只能有一个线程在执行。效率低。
10、ReentrantLock
代码:
注意 :ReentrantLock使用到的线程通信不是使用的wait()和notify()