day22
学习目标
1:线程的概念
2:Thread类和Runnable接口
3: 主线程与线程的生命周期
4:线程状态控制方法(sleep休眠状态)
5:线程安全threadsafe
学习内容
一多线程
我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?要解决上述问题,咱们得使用多进程或者多线程来解决。
并发与并行
井发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
在操作系统中,安装了多个程序,并发指的是在一-段时间内宏观上有多个程序同时运行,这在单CPU系统中,每时刻只能有-道程序执行,即微观上这些程序
是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个CPU系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU) .实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程
序这样多个程序便可以同时执行。目前电脑市场上说的多核CPU.便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理线程也是一样的, 从宏观角度上理解线程是并
行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行, 当系统只有一个CPU时, 线程以某种顺序执行多个线程,我们把这种情况称之为线程调度。
线程与进程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间, 一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运
行程序的基本单位;系统运行一个程序即是一 个进程从创建、 运行到消的过程。
线程: 线程是进程中的一 个执行单元,负责当前进程中程序的执行, 一个进程中至少有一 个线程。一个进程中是可以有多个线程的, 这个应用程序也可以称之为多线程程序。
简而言之: 一个程序运行后至少有一个进程, 一个进程中可以包含多个线程
我们可以再电脑底部任务栏,右键—>打开任务管理器可以查看当前任务的进程
线程调度:
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java便用的为抢占式调度。
抢占式调度详解
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,- 边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,“感觉这些软件好像在同一时刻运行着”。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
创建线程类
Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即-段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run(方法, 该run0)方法的方法体就代表了线程需 要完成的任务因此把run)方法称为线程执行体。
2.创建Thread子类的实例,即创建了线程对象
3.调用线程对象的star()方法来启动该线程
创建线程方式二
采用java.lang . Runnable也是非常常见的一-种,我们只需要重写run方法即可。步骤如下:
1.定义Runnable接口的实现类,并重写该接口的run()方法,该run(方法的方法体同样是该线程的线程执行体。2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象, 该Thread对象才是真正的线程对象。
3.调用线程对象的start()方法来启动线程。
二:Thread类和Runnable接口
thread类
Java中,创建线程的方法有两种:一是通过继承线程类Thread来创建线程;二是建立一个实现Runnable接口的类。
Runnable接口
从 NEW 到 RUNNABLE 状态
Java 刚创建出来的 Thread 对象就是 NEW 状态,而创建 Thread 对象主要有两种方法。一种是继承 Thread 对象,重写 run() 方法。示例代码如下:
MyThread myThread = new MyThread();
另一种是实现 Runnable 接口,重写 run() 方法,并将该实现类作为创建 Thread 对象的参数。示例代码如下:
// 实现 Runnable 接口
class Runner implements Runnable {
@Override
public void run() {
// 线程需要执行的代码
…
}
}
// 创建线程对象
Thread thread = new Thread(new Runner());
步骤如下:1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法(将线程要运行的代码存放在run方法)
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
(因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法 ,就必须明确改run方法所属的对象)
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
和继承Thread的实现多线程的区别如下:
1.避免了单继承的局限性(在定义线程是建议使用Runnable接口实现)
2.存储位置不同(继承方式:线程代码存放Thread子类的方法中
实现Runnable 线程代码存放在接口的子类的run方法中
三: 主线程与线程的生命周期
通用的线程生命周期基本上可以用下图这个“五态模型”来描述。这五态分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。
四:线程状态控制方法(sleep休眠状态)
sleep方法是Thread类中的一个静态方法,当一个执行中的线程调用了Thread的sleep方法之后,调用线程会暂时让出指定时间的执行权,这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有且不让出的。指定的睡眠时间到了之后,sleep函数会正常返回,线程就处于就绪状态,然后参与CPU调度,获取到CPU的资源后就可以运行了。
五:线程安全threadsafe
线程之间存在“竞争条件”,作用于同一个mutable数据上的多个线程,彼此之间存在对该数据的访问竞争并导致interleaving,导致post-condition可能被违反,这是不安全的。
线程安全:ADT或方法在多线程中要执行正确。
要做到:不违反spec,保持RI。与多少处理器,如何调度线程无关,不需要在spec中强制要求client满足某种“线程安全”的义务。
例如:Iterator就不是线程安全的,因为你不能在迭代遍历的时候改变collection。
保证线程安全,有四种策略:
(1)限制数据共享(Confinement)
(2)共享不可变数据(Immutability)
(3)共享线程安全的可变数据(Threadsafe data type)
(4)同步机制(Synchronization)