并行与并发

进程与线程的基本概念
什么是进程?
程序就是静态的文本,像乐谱一样。程序执行的时候,会从硬盘进入内存,内存中的程序就叫进程,就像正在演奏的乐谱。

进程是计算机要执行的一个独立的计算任务,不同任务,时间花费不同。为了避免短任务长时间等待长任务的情况,计算机操作系统调度CPU在不同的任务之间进行轮转。
什么是线程?
一个进程也是由多个子任务组成的,这些子任务就称为线程,是程序执行的最小单位。比如对于一个监控系统来说,它不仅要把图像数据显示在画面上,还要与服务端进行通信获取图像数据,还要处理人们的交互操作。有了多线程就可以满足实时响应的需求。

进程是操作系统进行资源分配的基本单位,而线程是CPU调度的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
既然多个线程是共同占有所属进程的资源和地址空间的,就会有冲突,协调办法在线程安全中讨论。
进程和线程的区别
- 进程是操作系统分配资源的最小单位,线程是程序执行(也即CPU调度)的最小单位。
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 进程之间相互独立。同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),所以线程会有安全问题。
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
浏览器内核是多线程的程序
在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:
- GUI 渲染线程
- JavaScript引擎线程
JavaScript为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果JavaScript是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突; 如果Javascript是多线程的话,在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript在最初就选择了单线程执行。 - 定时触发器线程
浏览器定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果JavaScript引擎处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。 - 事件触发线程
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。 - 异步http请求线程
在Java中创建线程:
- 继承 Thread 类
- 实现 Runnable 接口,传入到 Thread 对象中。
- 实现 Callable 接口
好处:可以有返回值;可以抛出异常。
弊端:代码比较复杂,所以一般不用
区别:
- 由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
- 如果只是想共享任务,那就没有必要及继承一些用不到的属性和方法,实现接口即可。
- 实现Runnable接口只能定义任务,不能直接使用Thread类中的方法。要想使用,需要首先获取当前线程对象。
使用匿名内部类创建线程
//新创建一个线程,重写run()方法。不重写方法并不会报编译错误,因为Thread类有run方法的实现。
new Thread() {
@Override
public void run() {
super.run();
// code
}
}.start();
//新创建一个线程,将Runnable对象作为构造器参数传递
new Thread(new Runnable() {
@Override
public void run() {
// code
}
}).start();
线程的生命周期

枚举类 Thread.State 列出了线程的这6种状态:
- new:创建
- runnable:就绪,可运行的。但是不一定获得CPU时间片。
- blocked:被动的。被同步块儿(锁)或 IO 阻塞。所以IO并不占用CPU。
- waiting:主动的。调用了join、wait方法,不知道要等多久。wait是锁对象的方法。
- timed waiting:主动的。调用了sleep方法,并指定了时间。sleep是线程的方法。
- terminated:终止。run方法执行完毕或抛出异常

线程的上下文切换:存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。栈是私有的,不会被覆盖。
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加。
Thread类中的属性和方法
- 属性:线程名字(get/setName、构造器传入)、优先级、是否为守护线程、所要执行的Runnable对象(由构造器传入)。
- 方法
-
start():用来启动一个线程,在这个过程中,会为相应的线程分配需要的资源。
-
run():不需要用户来调用。当线程获得了CPU执行时间,便会自动调用。
- run方法有点 像psvm,是要被线程运行的代码。
- 通过start()方法去启动线程,而不是直接调用run()方法。run()方法中只是定义需要执行的任务,如果直接调用run()方法,会在当前线程中执行任务,并不会创建另外一个新的线程来执行任务。
- 注意:run方法里面的变量是线程私有的,因为线程拥有独立的栈(抽奖)。但是实现Runnable接口的类中的成员变量是线程共有的(卖票)。
-
sleep():静态方法。让当前线程睡眠,让出CPU,但不会释放锁。也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象(相当于在厕所睡着了)。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。
public static void main(String[] args) throws InterruptedException { System.out.println("1"); Thread.sleep(2000); System.out.println("2"); }
-
yield():该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。
-
join():等待别的线程。比如在线程A中调用B.join(),其后的代码会在B线程执行完毕后才会继续执行。实际上调用join方法是调用了Object的wait()方法,这个可以通过查看源码可知。wait()方法会释放线程占有的锁。
-
interrupt():
-
currentThread():静态方法,用于获得当前线程,很像 this 的功能。
-
参考链接:Java并发编程:Thread类的使用
网友评论