JavaSE入门级-第十八课-多线程
前言
计算机中所有具备功能性的代码整合我们称为程序,也可以说是软件,当这个软件在运行中我们称它是一个进程,一个进程会有很多组成部分,每一个组成部分就是一个线程,如果这个程序比较单一就只有一个部分,就是单线程程序(我们现在做的控制台就是单线程)
线程是进程执行的基本单位,他们是一起运行的(宏观)
比如王者荣耀

ü 人物控制,金币计算,地图场景,音效,语音,数据统计 这些都是同时运行,如果王者荣耀看成一个进程,那么这些都是它的线程,王者荣耀是一个多线程的程序
操作系统(知识补充)
l Operating System 是管理计算机硬件 和 软件资源的程序(是使用电脑的第一程序)操作系统经理过了几代的演变
l 单道审批系统
独享所有资源 CPU等待人工操作 穿孔的纸带 或者 卡片 I/O 问题严重,计算机运算比较快,大部分时间都在等待用户输入任务(作业)
l 批处理系统
晶体管,一次性输入,磁带上存储,载入内存操作,解决I/O问题,但是有任务等待问题还有速度执行问题
l 多道批处理系统
集成电路诞生,一次性读入多个作业,在读取输入的等待时间中,处理其他作业,无交互,等待时间长
l 分时系统
将CPU的使用权分成若干个细小的时间单位如30ms,保证雨露均沾,多个作业同时执行,宏观上
l 实时系统
人工智能机器人,需要依赖程序启动以后,动态采集数据
并行与并发
l 并行是指两个或者多个事件在同一时刻发生
l 并发是指两个或者多个事件在宏观上同时发生 太快了 1秒内 都是一起运行的,单个CPU在某一瞬间只能处理一个任务(所以我们考虑了多个CPU)
java.lang.Thread 线程
l 我们使用一个案例来看看究竟什么是线程
ü 葫芦娃 大战 孙悟空

ü 客户端代码:

ü 运行效果:

ü 两个对象交替运行,运行效果在两个对象间切换时间片
Thread类的常用方法
Thread表示java中的线程,不需要额外导入包,每个线程都拥有优先权,调用了Runtime.exit() 退出线程,run()方法为线程的执行方法创建线程
ü 方法列表部分:
|
方法名 |
方法功能 |
|
Thread () |
创建一个线程对象 |
|
创建一个线程对象 |
|
|
创建一个线程对象 |
|
|
创建一个线程对象 |
|
|
Thread |
创建一个线程对象 |
|
getId |
返回线程的表示ID |
|
getName |
返回线程名称 |
|
返回线程优先级 |
|
|
getState |
返回线程状态 |
|
join |
等待这个线程死亡 |
|
run |
可执行方法,Runable |
|
start |
导致此线程开始执行;调用此线程的 |
|
sleep |
线程休眠,等待唤醒 |
ü 注意:run() 方法是是运行方法,start() 是改变线程转态
java.lang.Runnable 接口
l 实现线程的第二种方式
ü 代码示例:

ü 这是一个函数式接口
ü 使用Runnable实现线程

ü 客户端调用:
ü 解析:run保证方法名是相同的,本质上还是需要Thread的start()方法
多线程经典案例
案例1:泡茶
ü 客人来家里泡茶,洗杯子线程,烧水线程同时开启

ü 客户端代码:

ü 运行效果

案例2:ATM取款
ü ATM有多台机器,去ATM上面取款应该是并发运行

ü 客户端代码:

ü 解析:金额总量是固定的,ATM机有3个,3个同时运行不需要相互等待
练习:
l 实现多线程窗口售票,或多线程食堂打饭
Java中的多线程
l java主攻互联网服务,在这个方向上的多线程有不太一样的地方,多线程可以分为两种方向展现:
ü 一遍泡茶一遍洗杯子,是两个事件,两个类,两个对象,同时运行
ü 在100米赛道上,是一个时间,一个类,八个对象,同时运行
ü 从编码的角度说,两个类容易理解,但是连个对象就比较困难,如果两个对象共同访问同一份静态数据,说着是类共享数据,就会出现 线程安全问题
ü 代码实例:

ü 这个钱,会出错
同步 与 异步
l 线程同步与异步指的是资源的占用问题
ü 线程同步 synchronization:如果多个线程同时需要访问资源,线程A访问资源时必须 等待 其他线程没有访问改资源,称为线程同步,或 线程安全
ü 线程异步 asynchronization:如果多个线程同时需要访问资源,线程A访问资源 无需等待 其他线程,称为线程异步,或 线程不安全
优缺点比较
l 线程同步,数据数据安全,但是线程之间相互等待,效率比较低,线程异步不需要等待效率高,但是有数据污染风险
synchronized
ü 方法同步:

ü 代码解析:synchronized 是一个关键字,加载方法中,表示方法同步,即 有线程进入到方法中,方法访问的变量数据安全,必须等待次线程结束之后才能被其他线程访问
l 代码块同步:

ü 当对象进入代码块中,同步锁一样的效果,数据安全
线程死锁问题:
l 两个线程相互等待对方释放资源,就是死锁


l 客户端调用:

ü 谁先放手谁是狗
形成死锁的前提条件
ü 互斥条件:系统对于资源的分配,一次只能给一个进程
ü 不可剥夺条件:资源获得之后,除非主动释放否则不能强行剥夺
ü 请求与保持条件:对自己持有的资源保持不放,并且提出新的请求
ü 循环等待条件:存在资源等待循环(闭环)
l 四个条件为死锁必要条件
避免死锁问题
l 死锁没得救,一定会发生,但是我们可以减少它发生的概率
ü 使用顺序锁
ü 使用限时锁
ü 死锁检测
ü 添加中间层
练习:
l 银行家算法
线程状态
l 线程处在不同的阶段能够定义出不同状态
l 如下图所示:

l 总结状态:
ü new新建:线程被创建,new Thread();
ü runnable就绪:调用了 start() 方法,在线程池线程中等待运行
ü running运行:获得时间片,得到CPU权限
ü blocked阻塞:失去时间片,暂停运行
- 等待阻塞:wait()方法,让出时间片
- 同步阻塞:需要访问的资源被 synchronized 锁了
- 其他阻塞:调用sleep() 或者 join() 或者 请求IO
ü dead死亡:线程正常结束,或被杀死
线程转态转换方法:
新建 → 就绪
l 调用 start() 方法,调run() 方法是没用滴,run方法是给jvm调用的

就绪 → 运行
l 自然获得时间片,或者提高线程优先级(插队),优先级分为 1-10越大越牛逼
运行 → 死亡
l main()方法结束,run()方法结束,或者背调用命令 kill
运行 → 阻塞 → 运行
sleep()方法
l 线程执行sleep()方法,进入阻塞状态,但是不会释放资源锁

sleep 时间结束后,不是马上run,需要等待时间片
wait()方法
l 线程调用wait方法,进入阻塞状态,会释放锁,必须拥有锁,否则不能直接wait()

wait(),和 notify()必须在 同步代码块内
l wait(时间)时间到了会自己抢锁执行

l 当发生以下事件会被唤醒:
- 被notify 或者 notifyAll
- 没有其他线程了
- 经过限定时间以后
yield() 方法
l 暂停当前的线程,不会释放锁,从新让线程回到就绪状态,等待时间片

join()方法
l 把指定线程加入到当前方法中,被加入线程执行结束后才会继续执行原线程

Callable 接口(补充)
l Callable也是函数式接口,只有一个方法,实现方式和 Runnable类似,但是Callable能够在执行方法结束后,获得返回值
代码示例:


ü 可以使用 FutureTak .get()方法 获得返回值
线程池 调度工具
Java通过Executors提供四种线程池
- SingleThreadExecutor 单线程
- FixedThreadPool 控制并发数
- ScheduledThreadPool 预定线程,时间固定
- CachedThreadPool 缓存线程
Lock可重入锁
使用关键字synchrognized,能够解决同步问题但是语义表达不清晰,java的锁对象

ThreadLocal
l ThreadLocal解决并发问题,是线程的一个本地变量,为每个线程提供独立的副本使得线程之间不会相互影响
代码示例:

生产者 与 消费者(补充)
l 生产者 与 消费者模式指的是一种数据提供机制,java中使用线程的等待和唤醒来实现这类问题
l 如图所示:

ü 生产者提供数据,到数据缓冲区,消费者从数据缓冲区取数据
ü 缓冲区存在数据时,允许消费者获取
ü 缓冲区数据空缺时通知生产者制造
l java实现代码如下:
数据类型:

生产者:

消费者:

缓冲区:

客户端代码:


浙公网安备 33010602011771号