文章目录
- 多线程
- 一.什么是多线程
- 二.多线程的两个概念
- 三.线程的实现方式
- 四.常见的成员方法
- 五.线程安全的问题
- 六.死锁
- 七.生产者和消费者
多线程
一.什么是多线程
进程:是程序的基本执行实体
理解:每一个运行的软件就是一个进程
线程:是操做系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位
理解:应用软件中互相独立,可以同时运行的功能
为什么要有多线程?
有了多线程就可以让程序同时做多件事情
可以提高程序的运行效率
多线程的应用场景
- 软件中的耗时操做
- 拷贝,迁移大文件
- 加载大量的资源文件
- 所有的聊天软件
- 所有的后台进程
二.多线程的两个概念
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时,那么此时虚拟机就会把当前线程交给操做系统
去管理了,所以就没有定义运行状态