操作系统发展回顾
简单批处理系统->多道批处理系统->分时处理
进程
进程的概念和特点
资源由操作系统分配(内存、I/O等)。
调度(操作系统为它管理的进程分配时间片),执行。
进程的状态
新建、就绪、运行、阻塞、退出。
- 新建:分配进程ID、优先级。
- 就绪:进程相关的数据创建完毕,放在就绪队列。
- 运行:进程被操作系统分配了时间片,处理器开始执行它。
- 阻塞:I/O阻塞等。放入阻塞队列。
- 退出:进程执行完毕。
线程
线程的概念和特点
可以被调度和执行的单位通常被称作线程或者轻量级线程。
- 一个进程至少对应一个线程。
- 各个线程可以共享同一个进程中的各种资源。
- 创建、切换、销毁线程的成本远低于原先进程的成本。
- 线程通信比进程通信效率高。
线程的状态
创建、就绪、执行、阻塞、退出。
java线程
main线程
main线程是系统创建用来执行我们的程序。
任务
java中的任务被抽象成了一个Runnable接口。
但是只有任务没什么用,需要创建一个线程去运行这个任务。
Thread类
java中的Thread类来代表一个线程,我们需要关注它的这几种构造方法:
- Thread(Runnable target, String name):在创建线程对象的时候传入需要执行的任务以及这个线程的名称。
- Thread(Runnable target):只传入需要执行的任务,名称是系统自动生成的,或者可以在创建对象后再通过别的方法修改名称。
- Thread(String name):只传入待创建线程的名称。
- Thread():啥都不传,就是单纯构造一个线程对象而已。
执行任务,Thread类的start()方法负责开始执行一个线程,让一个线程运行起来有这么两种方法:
- 创建Thread对象的时候指定需要执行的任务。
- 通过继承Thread类并覆盖run方法。
这两种执行任务的方法说不上谁好谁坏,但是使用继承Thread类并且覆盖run方法的方式把线程和任务给弄到了一块儿,不可分割了,也就是所谓的耦合了,所以我们平时更倾向于使用任务和线程分开处理的第1种执行任务的方式。当然,有时候为了演示的方便,也是会使用继承Thread类并且覆盖run方法的方式。
线程相关方法
Thread类提供了许多方法来方便我们获取线程的信息或者控制线程。
- 获取线程ID。
- 获取和设置线程优先级。
- 休眠。
- 获取当前正在执行的线程。
- 守护线程。
- 让出本次处理器时间片。
- 等待另一线程执行结束。join()。
void join():等待该线程终止才能继续执行。
void join(long millis):在指定毫秒数内等待该线程终止,如果到达了指定时间就继续之行了。
void join(long millis, int nanos):跟上边方法一个意思,只不过加了个纳秒限制。
原子性操作
线程风险
共享变量:堆内存共享
原子性操作
一个或某几个操作只能在一个线程执行完之后,另一个线程才能开始执行该操作,也就是说这些操作是不可分割的,线程不能在这些操作上交替执行。
我们需要保证操作的原子性。
从共享性解决
- 尽量使用局部变量解决问题。
- 使用ThreadLocal类解决问题。不同线程操作同一个ThreadLocal对象执行各种操作而不会影响其他线程里的值。
从可变性解决
让某个变量在程序运行过程中不可变,把它使用final修饰。
加锁解决
加锁的语法:
synchronized (锁对象) {
需要保持原子性的一系列代码
}
锁的重入:只要一个线程持有了某个锁,那么它就可以进入任何被这个锁保护的代码块。
对于成员方法来说,我们可以直接用this作为锁。对于静态方法来说,我们可以直接用Class对象作为锁(Class对象可以直接在任何地方访问)。