文章目录
- 多线程
- 一.什么是多线程
- 二.多线程的两个概念
- 三.线程的实现方式
- 四.常见的成员方法
- 五.线程安全的问题
- 六.死锁
- 七.生产者和消费者
 
多线程
一.什么是多线程
进程:是程序的基本执行实体
理解:每一个运行的软件就是一个进程
线程:是操做系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位
理解:应用软件中互相独立,可以同时运行的功能
为什么要有多线程?

有了多线程就可以让程序同时做多件事情
可以提高程序的运行效率
多线程的应用场景
- 软件中的耗时操做
- 拷贝,迁移大文件
- 加载大量的资源文件
- 所有的聊天软件
- 所有的后台进程
二.多线程的两个概念
1.并发
同一时刻,有多个指令在单个cpu上交替执行
2.并行
同一时刻,有多个指令在多个cpu上同时执行
并发和并行是有可能都在发生的
三.线程的实现方式
继承Thread类的方式
- 定义一个类继承Thread
- 重写run方法
- 创建子类的对象,并启动线程

实现Runnable接口的方式
- 定义一个类实现Runnable接口
- 重写里面的run方法
- 创建自己的类的对象
- 创建一个Thread类对象,并开启线程

细节:getName()是Thread类中的方法,所以想要调用,则
需要获取到当前线程的对象,然后用这个对象去调用
利用Callable接口和Future接口的方式
**特点:**可以获取到多线程运行的结果
- 创建一个类MyCallable实现Callable接口
- 重写call(是有返回值的,表示多线程运行的结果)
- 创建MyCallable的对象(表示多线程要执行的任务)
- 创建FutureTask的对象(管理多线程运行的结果)
- 创建Thread类对象,并启动(表示线程)

多线程三种实现方式对比

四.常见的成员方法
Thread类中常见的成员方法


细节:
1.如果我们没有给线程设置名字,线程也是有默认名字的
格式:Thread-x(x是序号,从0开始)
2.如果我们要给线程设置名字,可以用set方法进行设置,也可以用构造方法进行设置

细节:
1.当JVM虚拟机启动之后,会自动的启动多条线程,其中有一条线程就叫做main线程
它的作用就是调用main方法,并执行里面的代码
2.哪条线程执行到sleep(),那么哪条线程就停留对应的时间
3.参数:时间,单位:毫秒

在计算机当中,线程的调度有两种
第一种,抢占式调度
所谓抢占式调度,就是多个线程在抢夺cpu的执行权
cpu在什么时候执行哪条线程是不确定的,执行多长时间也是不确定的,体现随机性
第二种方式,非抢占式调度
所有的线程轮流的执行
在java中,采用的是第一种,抢占式调度
优先级越大,线程抢到cpu的概率就越大
优先级最低:1
最高:10
默认:5


细节:
当其他的非守护线程执行完毕之后,守护线程会陆续结束
守护线程的应用场景:
比如,聊天窗口,既可以聊天,也可以发文件
把聊天看成一个线程,发文件看成另一个线程
此时,如果把聊天窗口关掉,那么发文件线程也就没有存在的必要了
所以,就可以把发文件线程设置为守护线程

尽可能的让出cpu


线程的生命周期

五.线程安全的问题
买票引发的安全问题
- 相同的票出现了多次
- 出现了超出范围的票


线程执行时,有随机性
同步代码块
把操做共享数据的代码锁起来
格式:

特点:
- 锁默认打开,有一个线程进去了,锁自动关闭
- 里面的代码全部执行完毕,线程出来,锁自动打开
锁对象是任意的对象,但是要保证锁对象是唯一的
static Object obj = new Object();
同步代码块的两个细节
-  锁对象一定是唯一的,一般用字节码文件对象: 类名.class
-  synchronized一定要写在循环的 里面说明:如果把synchronized写在了循环的外面,那么其他线程将没有机会去执行任务,任务都被线程一执行完了 
同步方法
如果我们想把一个方法里面的所有代码都锁起来,那么就没有必要使用同步代码块了
可以把synchronized直接加到方法上,那么这个方法就是同步方法
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){}
特点:
- 同步方法是锁住方法里面的所有代码
- 锁对象不能自己指定- 如果当前的方法是非静态的,它的锁对象是this,就是当前方法的调用者
- 如果当前方法是静态的,它的锁对象是当前类的字节码文件对象
 
- 如果当前的方法是
写同步方法的技巧:
先写同步代码块,然后把同步代码块抽取成一个方法
lock锁
在JDK5以后提供了一个新的锁对象Lock
它的实现比synchronized有更广泛的锁定操做
它提供了两个方法:
void lock():获得锁
void unlock():释放锁
这样就可以实现手动上锁和手动释放锁
注意:
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法:ReentrantLock(),创建一个ReentrantLock的实例

六.死锁
就是在程序中出现了锁的嵌套

七.生产者和消费者
又叫做等待唤醒机制
生产者消费者模式是一个十分经典的多线程协作的模式
我们知道多线程的执行具有随机性
那么,现在我们学习的生产者和消费者模式就要打破这一特性,使得多个线程轮流执行
生产者:生产数据
消费者:消费数据

理想情况
生产者先抢到cpu的执行权,进行生产数据,然后消费者再抢到cpu的执行权,进行消费数据
消费者等待
消费者先抢到cpu的执行权,这时发现没有数据,此时消费者处于等待状态,
当消费者处于等待状态时,生产者会抢到cpu的执行权,判断是否有数据,
如果没有,则生产数据,然后唤醒消费者,消费者进行消费数据

生产者等待
生产者先抢到cpu的执行权,判断是否有数据,有则进行等待


编写多线程代码的套路
- 循环
- 同步代码块
- 判断共享数据是否到了末尾(到了末尾)
- 判断共享数据是否到了末尾(没到末尾)
阻塞队列方式实现等待唤醒机制
阻塞队列好比是连接生产者和消费者之间的管道
生产者可以把生产的数据放到管道当中
消费者可以在管道中获取数据进而进行消费
我们可以规定管道中最多可以放多少数据
队列:数据在管道中,好比是排队一样,先进的数据,先出去
阻塞:
放数据时,放不进去,会等着,叫阻塞
取数据时,取不到,也会等着,也叫阻塞
阻塞队列的继承结构
阻塞队列一共实现了4个接口
iterable:表示阻塞队列可利用迭代器进行遍历或者增强for循环
collection:表示阻塞队列是单列集合
Queue:表示队列
BlockingQueue:表示阻塞队列
实现类:
ArrayBlockingQueue:底层是数组,有界,创建对象时必须指定长度
LinkedBlockingQueue:底层是链表,无界
不是真正的无界,最大为int的最大值
多线程的6种状态

在Java虚拟机当中,关于线程的状态,真正定义的只有6种状态,是没有定义运行状态的,
因为,当线程抢到cpu时,那么此时虚拟机就会把当前线程交给操做系统去管理了,所以就没有定义运行状态


![[unity]保存文件的路径设置](https://img-blog.csdnimg.cn/02fc94df4ae44579b0051ad8b21e4bca.png)
















