多线程

# 多线程
## 一、进程和线程
0. 比喻: 进程好比航空母舰,线程好比舰载机
1. 进程(process):进程是操作系统进行资源分配和调度的最小单元
2. 线程(Thread):线程是CPU调度执行任务的最小单元
3. 进程和线程之间的关系
1). 一个软件启动,至少会启动一个进程
2). 一个进程运行,至少有一个线程
3). 进程的资源是向系统申请的,线程的资源是进程提供的
## 二、并行和并发
1. 并行:同一时间点,一起执行
(在同一时刻,有多个指令在多个CPU上同时执行)
2. 并发:同一个时间段中,一起发生
(在一段时间内,有多个指令在单个CPU上交替执行)
3. 结合计算机的知识
0). 前提
I. 一个线程只能执行一个任务
II. 线程执行需要CPU分配执行权
1). CPU:8核16线程
I. CPU的核心数有8个
II. 同时支持16个线程执行
2). 并行:并行16个线程
3). 并发:并发2700个线程
## 三、CPU的调度算法
0. 介绍:指的是CPU给线程分配执行权的方式
1. 分时调度
平均分
2. 抢占式调度(java)
CPU倾向分配给优先级高的线程
随机性
## 四、学习多线程的原因
1. 让我们的java程序可以同时执行多个任务
2. 在硬件资源充足的前提下,可以提高效率
## 五、线程理解
1. 没有编写线程代码的清空
1). main线程: main方法所在的线程
2). GC线程: garbage collection 垃圾回收器线程
2. 一个线程只能一件事
3. 多线程并发是没有规律: 抢占式调度具有极强的随机性
我们无法预测某个时间哪个线程在运行
4. 线程之间是相互独立的
1). 一个进程只要有一个线程在执行,这个进程就不会结束
2). 同一个进程中,线程的栈内存是独立的,堆内存是共享的
线程A异常不处理,被JVM中止,不会影响其他线程
## 六、多线程的实现方式(一)
1. 实现多线程的方式有哪些?
1). 继承Thread类的方式进行实现
2). 实现Runnable接口的方式进行实现
3). 利用Callable和Future接口方式实现
2. Thread类
表示线程的类
3. 方法介绍
void run()
在线程开始后,此方法将被调用执行
void start()
在此线程开始执行,Java虚拟机会调用此线程的run方法()
4. 实现步骤
1). 定义一个类MyThread继承Thread类
2). 在MyThread类中重写run()方法
3). 创建MyThread类的对象
4). 启动线程
5. 第一种: 继承Thread类的方式进行实现
1). 定义一个类继承Thread
2). 重写run方法(编写任务)
3). 创建线程对象,并调用start方法
问题: run方法和start方法的区别
I. run方法: 只是普通方法,不会开启线程
II. start方法:
JVM底层会创建一个线程,并调用此线程的run方法
6. 第二种: 实现Runnable接口
1). 编写一个类实现Runnable接口
2). 重写run方法(编写任务)
3). 创建线程对象,将Runnable实现类对象作为参数传入,并启动
## 七、多线程的实现方式——两个小问题
1. 为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
2. run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
## 八、多线程的实现方式(二): 实现Runnable接口
1. Thread构造方法
Thread(Runnable target)
分配一个新的Thread对象
2. 实现步骤
1). 定义一个类MyRunnable实现Runnable接口
2). 在MyRunnable类中重写run()方法
3). 创建MyRunnable类的对象
4). 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
5). 启动线程
## 九、多线程的实现方式(三): 实现Callable接口
1. 方法
V call()
计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)
创建一个FutureTask,一旦运行就执行给定的Callable
V get()
如有必要,等待计算完成,然后获得其结果
2. 实现步骤
1). 定义一个类MyCallable实现Callable接口
2). 在MyCallable类中重写call()方法
3). 创建MyCallable类的对象
4). 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
5). 启动线程
6). 再调用get()方法,就可以获取线程结束之后的结果
3. 注意事项
get()方法的调用一定要在Thread类对象调用start()方法之后
## 十、三种实现方式的对比
1. 实现Runnable、Callable接口
- 好处:扩展性强,实现该接口的同时还可以继承其他的类
- 缺点:编程相对复杂,不能直接使用Thread类中的方法
> 推荐Runnable!
2. 继承Thread类
- 好处:编程比较简单,可以直接使用Thread类中的方法
- 缺点:扩展性较差,不能再继承其他的类
3. 实际开发过程中如何选择?
如果不需要任务的返回结果,就实现Runnable接口,需要的话,实现Callable接口。
4. 如何去写多线程程序
1). 确定要做什么,执行什么任务
2). 要不要用多线程,要任务返回的结果,如果要 实现Callable接口,如果不要 实现Runnable接口
3). 实现call方法或run方法
4). 交给线程类,执行start(),开启线程干活
## 十一、线程名字
1. 默认
1). 主线程: main
2). 子进程:
Thread-0, 1, 2, 3, 4...
2. 设置
1). void setName(String name)
设置线程名字
2). Thread.currentThread().getName()
获取当前线程名字
## 十二、线程类中的常见方法
1. 设置和获取线程名称(重点)
- 方法介绍
void setName(String name)
将此线程的名称更改为等于参数name
String getName()
返回此线程的名称
2. Thread方法——获得当前线程对象(重点)
- 方法介绍
static Thread currentThread()
返回对当前正在执行的线程对象的引用
3. 线程休眠(重点)
- 方法介绍
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
4. 线程优先级
- 方法介绍
final int getPriority()
返回此线程的优先级
final void setPriority(int newPriority)
更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
5. 守护线程
1). 什么是守护线程?
守护其他线程,当普通线程执行完毕了,守护线程就没有存在的必要了
2). 如何使用守护线程?
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将推出
## 十三、线程同步
1. 线程安全问题——原因分析
所谓的线程安全问题(线程同步问题)指的是多个线程争抢同一资源,而出现的数据结果与预期不一致的情况。
> 首先要有多线程,其次多个线程要争抢同一资源
2. 卖票出现重复票和乱序问题的原因
卖票的过程中有多条线程操作了共享数据
java用的CPU调度算法是抢占式调度,不具备规律性,很随机
解决: 控制不了CPU,就控制我们自己的程序
方案: 同步锁
3. 同步代码块解决数据安全问题
1). 如何解决上述问题?
I. 任意时刻只有一条线程可以操作(增、删、改、查)共享变量
II. Java中如何解决?
同步代码块格式:
synchronized(任意对象) {
操作共享数据的代码
}
synchronized(任意对象): 就相当于给代码加锁了,任意对象就可以看成是一把锁
2). synchronized同步代码块的特点
默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完出来时,锁才会自动打开
3). 什么情况下会出现线程安全问题呢?
多条线程操作共享数据(可以拆分为三个条件):
I. 多线程环境
II. 有共享数据
III. 多条线程操作了共享数据
因此,在卖票的时候,加不加Thread.sleep()代码中都会有线程安全问题,只不过加上之后,线程执行的速度慢了下来,我们可以观察到问题
posted @   青核桃啊  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示