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,线程同步机制