You are on page 1of 4

一、多线程

1.多线程概念
进程:操作系统中独立运行的程序或服务通常就是一个进程。计算机中可以同时运行多个进程。

线程:一个进程内部,还可以分割为多个并发的线程,每个线程都可以独立的执行相关的任务。
一个线程可以认为是程序执行的一条执行路径,在需要时可以在程序中开辟更多执行路径来并行的执行任务。当
在一个进程中开启多个线程从而在并行的执行多个任务时,这就是一个多线程的程序。

当程序开启多个线程时,多个线程抢夺 cpu,哪个线程抢夺到哪个线程就可以执行,通过不停的
抢夺 cpu,多个线程都可以得到执行,而 cpu 处理的速度非常的快,切换线程也非常的快,所以在人看起来这些
线程似乎都在并行的执行。

java 本身就支持多线程的开发。

jvm 虚拟机本身也是个进程,可以通过代码在 jvm 虚拟机进程中开启多个线程,从而开发多线程


的程序。

2.java 中多线程的开发
java 中有代表线程的类,java.lang.Thread
在这个类中,提供了创建线程、启动线程、操作线程、控制线程等相关的方法,是 java 多线程
开发的核心类。

java 开发多线程程序
方式 1:
写一个类继承 Thread 类 覆盖父类中的 run 方法 在 run 方法中实现线程要执行
的代码 就可以开发出一个线程了
线程开发完成后并不会自动执行,需要创建改类的对象后 调用该对象的 start
方法,启动线程。

案例:用方式 1 开启多线程

方式 2:
写一个类实现 Runnable 接口,在实现类中实现接口中定义的 run 方法,在 run
方法中编写线程要执行的代码。
new Thread(runnable)在参数中传入 runnable 实现类的对象,再调用
start()方法 就可以启动线程

案例:用方式 2 开启多线程

通过观察发现,多个线程的执行的先后顺序并没有任何规律,谁抢到 cpu 谁就执行。

3.线程的状态切换
线程由如下的几个状态
未启动:线程对象被创建出来,但是还没有执行 start(),并未执行起来
冻结:线程启动了起来,有抢夺执行权限,参与抢夺 cpu,但是未抢到 cpu 状态下,不能
得到执行。
执行:线程启动了起来,抢到了 cpu,在执行
挂起:线程启动了起来,但是通过 sleep 或 wait 进入挂起状态,没有执行权限,不再抢
夺 cpu,直到 sleep 结束或 wait()被 notify 或 notifyAll 方法唤醒,重新进入冻结状态,开始抢夺 cpu
结束:线程启动了起来,执行结束或被中断,则线程结束,释放占用的内存和 cpu。

状态转换的过程:参考图

4.线程中的常用方法
static Thread currentThread() 获取当前正在执行的线程
long getId() 获取线程的编号,每个线程在执行的过程中,jvm 都会为其分配独一无二的编
号来唯一的进行表示,可以通过这个方法获取线程的 id
String getName() 获取线程的名称,每个线程在执行的过程中,jvm 都会为其分配一个名
称,这个名称可以通过 getName 来获取
void setName(String name) 设置线程的名字
Thread.State getState() 获取线程的状态
void interrupt() 中断线程
static void sleep(long millis) 让当前线程进入休眠状态,被挂起,放弃执行权,不再
抢夺 cpu,持续指定的时间
int getPriority() 获取当前线程的优先级
void setPriority(int newPriority) 设置当前线程的优先级 优先级的取值范围为
1~10 越大优先级越高 如果不设置 优先级为 5
**注意,Thread 中虽然提供了 stop 方法,但是此方法已经被过时,不再有效,并且没有提供替
代的方法,因为在线程执行的过程中强制退出,会具有固有的不安全性,可能造成很多意外的不符合预期的结果,
所以 Thread 中现在没有一个明确的退出线程的方法,如果想要正常的退出线程,应该通过线程内代码的设计,
来实现线程可以根据不同的状态,正常退出。或者当线程真的无法正常退出,需要强制退出时,可以选择
interrupt 方法中断执行。

5.多线程并发安全问题
当多个线程并发执行 并且 操作了同一个共享资源时,由于线程抢夺 cpu,执行先后顺序是不确
定的,产生了一些意外的结果,这样的现象就称之为产生了多线程并发安全问题。

并发安全问题产生的条件:
多个线程参与
有共享资源
同时操作 且 对共享资源的修改

多线程安全问题的解决方法:
破坏产生同步安全问题的三个条件
多个线程参与 - 无法解决 要的就是多线程
有共享资源 - 取消共享资源 让每个线程都具有单独局部变量 可以解决问题 但
是并不适合于所有场景
同时操作涉及到修改 - 通常通过控制同时操作的行为解决问题

同步代码块:
利用同步代码块 利用锁对象 将多个操作共享资源的代码进行隔离
线程执行到同步代码块时 需要先得到锁对象上锁的控制权 才可以进入同步代码
块执行
这样多个线程 抢夺同一个锁对象上的锁 只有一个线程能够抢夺成功,同一时间
内只有一个线程可以获取到锁进入同步代码 来操作共享资源 从而保证了 同一时刻共享资源只被一个线程操作
避免了线程安全问题
如果有多个同步代码块需要同步 则需要多个同步代码块 选择同一个锁对象 关键
在于 必须使用同一个锁对象 同一把锁
同步代码块的锁对象 可以任意的选择 一般 常用的所对象 包括 共享资源对象本
身 类的字节码 this 等等

synchronized(lock){
需要同步的代码
}

同步方法:
在定义一个方法时 可以在返回值之前 写上一个 synchronized 关键字 则这个
方法就成为了一个同步方法 所有的线程在进入这个方法之前 都需要得到锁后才可以进入
非静态同步方法 将会使用 this 作为锁对象 --> 也就是调用该方法的对象
静态同步方法 没有 this 使用当前类的 class 对象作为锁对象

public synchronized void mx(){}

6.死锁
当具有多个共享资源时 一部分线程持有一部分资源的锁 要求另外的线程持有的另外的资源的锁
形成了各自持有各自的锁而要求对方的锁的状态 这样 进入了一个互相等待的状态 都无法继续执行 则称之为产
生了死锁的情况

死锁产生的条件:
多个共享资源
多个线程
同步嵌套 - 所谓的同步嵌套 就是多个 syncronized 代码块存在嵌套关系 这就意味着
会产生 持有一个资源 要求另一个资源的情况

解决死锁:
避免死锁
通过控制产生死锁的条件 保证不会产生死锁
多个共享资源 - 给每个线程都配置该资源 可以避免死锁 但是这并不是
所有的时候都可以实现的
多个线程 - 用单一线程完成任务 但是也不是所有的时候都可以做到的
同步嵌套 - 尽力的避免同步嵌套 是避免死锁的最好的方式 但是这并不
是所有的时候都可以实现的
检测死锁,打开死锁
检测死锁 看锁占用的情况是否形成了一个互相要求对方锁环
发现死锁后 打开死锁 异常退出一方 让其他线程可以得到足够的资源继续执行

7.线程之间的通信
多个线程 相当于进程中独立运行的 流程
每个线程都有各自的 内存空间 包括 栈 堆 都是独立的 无法互相访问对方声明的属性
可以认为 多个线程之间是互相隔离的

但是真正的开发的过程中 是有可能需要在线程之间 传递信息的 如何实现呢?

共享内存
共享内存机制 通过 在多个线程都可以访问到的内存位置保存并访问数据 来进行信息的
传递
一般用在 多个线程之间 需要 传递 属性 信息等场景下
案例:某一个线程通过控制一个布尔类型的信号量 控制另一个线程执行的流程

等待唤醒机制
一般是用来在协调过个线程执行先后顺序

在同步的过程中 必须选择一个锁对象 锁对象 可以是任意的对象 作为锁使用 并不会调


用该对象的任何属性和方法 也不会对该对象产生任何额外的影响 仅仅是使用该对象 作为一个锁标记 记录的位
置来使用
锁对象可以是任意选取的对象 而 java 中任何对象的类 都可以认为是由 Object 类派生
出来的 而在 Object 类上 定义有 和等待唤醒机制相关的方法
void wait() //使当前线程 进入挂起的状态 释放 cpu 放弃执行权 不再抢夺
cpu 释放当前占有的锁 进入一个挂起等待状态 直到被其他线程唤醒 退出挂起状态 如果一直没有其他线程唤醒
会一直等待下去
void wait(long timeout) //使当前线程 进入挂起的状态 释放 cpu 放弃
执行权 不再抢夺 cpu 释放当前占有的锁 进入一个挂起等待状态 直到被其他线程唤醒 或 等待超过指定的时间
退出挂起状态
void notify() //唤醒一个在当前锁上通过 wait 进入挂起状态的线程 如果
当前锁上有多个 wait 挂起的线程 唤醒哪一个是随机的 退出挂起状态并不意味着可以立即执行 仍然要抢夺到
cpu 才可以执行
void notifyAll() //唤醒所有在当前锁上通过 wait 进入挂起状态的线程
退出挂起状态并不意味着可以立即执行 仍然要抢夺到 cpu 才可以执行

所以 我们可以在同步代码块中 利用锁对象的这些方法 实现等待唤醒机制 从而控制多线


程执行的先后的顺序

案例:通过等待唤醒机制 实现 之前修改打印案例 修改和打印依次执行的效果

**sleep 和 wait 方法的区别:


sleep 是 Thread 对象上调用的 wait 是在锁对象上调用的
sleep 方法会设定一个超时时间时间结束后自动醒来 wait 方法可以设定也可以不设定
超时时间 可以通过 notify 或 notifyAll 唤醒 或如果设定过超时时间 在超时时间结束后自动醒来
wait 方法会使当前线程进入挂起状态 会释放 cpu 放弃执行权 不再抢夺 cpu 释放了锁
sleep 方法会使当前线程进入挂起状态 会释放 cpu 放弃执行权 不再抢夺 cpu 不会释放锁!!!!!

*案例(了解):实现一个多线程环境下的阻塞式队列的实现

8.多线程中其他的操作
join()
加入线程 当某个线程执行的时候 可以调用 join 将另外一个线程加入进来 则当前线程
会释放执行 cpu 并转让执行权 给加入进来的线程 直到加入进来的线程执行完成 当前线程才继续执行
1 号线程在执行。。。
调用 2 号线程的 join()
1 号线程挂起 将执行权转让给 2 号线程
2 号线程得到执行权 开始执行
直到 2 号线程执行结束 退出 将执行权再还给 1 号线程
1 号线程才继续执行。。。

setDaemon()
将指定线程设置为守护线程
守护线程和普通线程没有太大的区别,唯一的区别是,守护线程不能单独运行,如果进程
中所有非守护线程都结束了 则守护线程也会自动结束掉。

案例:完成王者荣耀

You might also like