JavaSe基础---多线程
# 1 - Java 中的多线程
# 1.1 - 进程
进程就是正在运行中的应用程序(进程是驻留在内存中)
# 1.2 - 线程
线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径
-
单线程:一个进程中包含一个顺序控制流
-
一、什么是线程,什么是进程
1. 进程是一个应用程序,资源分配的基本单位
2. 线程是一个进程中的执行场景 / 执行单元;一个进程可以启动多个线程,是程序执行的基本单位
二、对于 java 程序来说,当在 DOS 窗口输入命令后,会先启动 jvm,而 jvm 就是一个进程,同时再启动一个垃圾回收线程负责回收,至少再目前的 java 程序中,至少有两个线程并发,一个是垃圾回收线程,一个是执行 main 方法的主线程
三、进程 A 和进程 B 的内存独立不共享
四、** 线程 A 和线程 B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。** 假设有十个线程,会有 10 个栈空间,每个栈都是独立的互不干扰,各自执行各自的,这就是多线程并发。java**** 中的多线程机制目的就是为了提高程序的处理效率
五、** 执行一个线程就是执行该线程的 ****run ()** 方法中的代码
# 1.3 - 多线程的实现方式一
- 继承于 Thread 类
1、自定义一个 MyThread 类,去继承 Thread 类
2、在 MyThread 类中重写 run()方法
3、在测试类中创建 MyThread 类的对象
4、启动线程
1 |
|
# 1.4 - 设置和获取线程名
- 设置线程名
setName(String name)
: 设置线程名- 通过带参构造方法设置线程名
- 获取线程名
# 1.5 - 线程调度
- 线程调度模型
- 分时调度模型:所有的线程轮流使用 CPU,平均分配每个线程占用 CPU 的时间。
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么就会随机选择一个线程来执行,优先级高的占用 CPU 时间可能相对来说会高出那么一点点。
Java 中的 JVM 使用的就是抢占式调度模型
1 |
|
# 1.6 - 线程控制
方法名 | 说明 |
---|---|
void yield() |
使当前线程让步,重新回到争夺队列中 |
static void sleep(long ms) |
使当前正在运行的线程停留指定的毫秒数 |
void join() |
等死(等待当前线程销毁后,再执行其它线程) |
void setDaemon(boolean on/off) |
标记为守护线程,当运行的线程都是守护线程时,JVM 停止运行 |
- yield
1 |
|
- sleep
1 |
|
- join
1 |
|
- setDaemon
1 |
|
# 1.7 - 线程的生命周期
- 新建:创建线程对象(通过 start 方法进入到下一个状态)
- 就绪:有执行资格,但是没有执行权,需要抢 CPU 执行权
- 运行:有执行资格,并且也有执行权,但是也有可能会被其它线程抢走,如果遇到了阻塞式方法,会在阻塞式方法运行结束之后回到就绪状态,继续争夺 CPU 执行权
- 死亡:成为垃圾,等待被回收
# 1.8 - 线程的停止
停止线程意味着线程完成任务之前,需要结束正在执行的操作
1、使用标志的方法来退出程序
1 |
|
2 - 使用 stop 方法强制停止线程,导致出现异常,善后工作处理不完整同时会释放锁,可能导致数据不同步,出现数据安全的问题。
1 |
|
3 - 使用 interruput 和 isinterrupted 方法配合终止线程
1 |
|
# 1.9 - 多线程的实现方式二
- 实现 Runnable 接口
1、自定义 MyRunnable 类,去实现 Runnable 接口
2、在 MyRunnable 类中重写 run 方法
3、在测试类中创建 Thread 类的对象,并将 MyRunnable 类的对象作为参数传递到 Thread 类的构造方法中
4、启动线程
1 |
|
-
多线程实现方式
# 2 - 线程同步
# 2.1 - 卖票案例
需求:德云社北展剧场有一场演出,设置 100 张特价门票,三个销售点同时卖。
1 |
|
# 2.2 - 数据安全问题
- 是否具备多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
使用同步语句块
synchronized(任意对象){}
# 2.3 - 线程同步的利弊
# 2.4 - 同步方式
- 同步语句块:
synchronized (this){//同步语句块}
1 |
|
- 普通同步方法:
修饰符 synchronized 返回值类型 方法名(形参列表){方法体}
1 |
|
- 静态同步方法:
修饰符 synchronized static 返回值类型 方法名(形参列表){方法体}
1 |
|
# 2.5 - Lock
1 |
|
# 2.6 - 死锁
- 形成原因:
- 当两个或多个线程互相锁定时就形成了死锁。
- 避免死锁的原则
- 顺序上锁,反向解锁
1 |
|
# 3 - 生产者与消费者
生产者与消费者模式是并发、多线程编程中经典的设计模式,通过 wait 和 notifyAll 方法实现的。
简单来说,就是一个类负责生产,一个类负责消费,当生产者生产出商品后,需要等待消费者将商品消费掉。当消费者没有可以消费的商品时,需要等待生产者生产出商品,这其中就存在着线程之间协调配合的过程。
针对一个奶箱,送一瓶,才能取一瓶。
没送的话,取不到。
没取的话,也送不了
1 |
|
# 4 - Timer 定时器
1 |
|
# 5 - 关于多线程并发环境下,数据的安全问题
- 一、为什么是重点
- 项目是运行在服务器当中,服务器已经将线程定义,线程对象的创建,线程的启动等,这些代码不需要编写。需要注重的是数据在多线程并发的环境下是否是安全的
- 二、什么时候数据在多线程并发的环境下会存在安全问题
- 1. 多线程并发
- 2. 有共享数据
- 3. 共享数据有修改行为
- 三、如何解决线程安全问题
- 当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,解决办法:线程排队执行 **(不能并发)。用排队执行解决线程安全问题,这种机制被称为线程同步 ** 机制。此时会牺牲一部分效率
- 四、同步编程模型和异步编程模型
- 异步编程模型:线程 A 和线程 B,各自执行各自的,A 不需要等待 B,B 也不需要等待 A。其实就是多线程并发 (效率较高)
- 同步编程模型:线程 A 和线程 B,在线程 A 执行的时候必须等待线程 B 的执行,或者在线程 B 执行的时候必须等待线程 A 的执行结束,两个线程之间发生了等待关系,这就是同步编程模型,效率较低,线程排队执行
- 五、Java 中的三大变量
- 实例变量:在堆内存中
- 静态变量:在方法区中
- 局部变量:在栈内存中
- 以上三大变量中局部变量永远不会存在线程安全问题,因为局部变量不共享,实例变量和静态变量,一个在堆内存中一个在方法区中,堆和方法区都是多线程共享的,所以可能存在线程安全问题
- 六、如何解决线程安全问题
- 使用 synchronized (共享的对象){
- 同步线程代码块
- }
- 使用 synchronized (共享的对象){
- 七、在实例方法上可以使用 synchronized,并且出现在实例方法上,一定锁的是 this,不能是其他对象所以这种方式不灵活。另外还有一个缺点:synchronized 出现在实例方法上表示整个方法体需要同步,可能会无故扩大同步范围,导致程序执行效率降低。如果共享的对象是 this,并且需要同步的代码块是整个方法体,建议使用这种方式。
- synchronized 出现在静态方法是类锁,不管创建了几个对象,类锁只有一把
- 八、ArrayList,HashMap,HashSet 是非线程安全的;Vector,Hashtable 是线程安全的
- 九、synchronized 会让程序的执行效率降低,用户体验感不好,系统的用户吞吐量降低,用户体验感差,在不得已的情况下在选择线程同步机制
- 第一种方案:尽量使用局部变量代替实例变量和静态变量
- 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享
- 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择 synchronized,线程同步机制