进程与线程
1、进程
进程是资源(CPU,内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
2、线程
线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务(迅雷软件可以同时下载多个文件,每个下载任务就是一个线程),可以简单的认为进程是线程的集合。
线程是一条可执行的路径。
对于单核CPU而言:多线程就是一个CPU在来回的切换,在交替执行。
对于多核CPU而言:多线程就是同时有多条执行路径在同时(并行)执行,每个核执行一个线程,多个核就有可能是一块同时执行的。
3、进程与线程的关系
一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。亦即执行处理机调度的基本单位。进程和线程的关系:
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。即每个线程都有自己的堆栈和局部变量。
处理机分给线程,即真正在处理机上运行的是线程。
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师A进行解答,此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其他线程执行了,这样多个线程在来回的切换)
4、为什么要使用多线程
多线程可以提高程序的效率。
5、多线程应用场景
一般线程之间比较独立,互不影响
一个线程发生问题,一般不影响其它线程
多线程的实现方式
1、顺序编程
程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行结束后才能结束。
public class Main {
// 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚才能喝酒
public static void main(String[] args) throws Exception {
// 先吃饭再喝酒
eat();
drink();
}
private static void eat() throws Exception {
System.out.println("开始吃饭?...\t" + new Date());
Thread.sleep(5000);
System.out.println("结束吃饭?...\t" + new Date());
}
private static void drink() throws Exception {
System.out.println("开始喝酒?️...\t" + new Date());
Thread.sleep(5000);
System.out.println("结束喝酒?...\t" + new Date());
}
}
2、并发编程
多个任务可以同时做,常用与任务之间比较独立,互不影响。
线程上下文切换:
同一个时刻一个CPU只能做一件事情,即同一时刻只能一个线程中的部分代码,假如有两个线程,Thread-0和Thread-1,刚开始CPU说Thread-0你先执行,给你3毫秒时间,Thread-0执行了3毫秒时间,但是没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,当时的变量的值是多少,然后CPU说Thread-1你可以执行了,给你2毫秒的时间,Thread-1执行了2毫秒也没执行完,此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,当时的变量的值是多少,此时CPU又说Thread-0又该你,这次我给你5毫秒时间,去执行吧,此时CPU就找出上次Thread-0线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续执行,结果用了2毫秒就Thread-0就执行完了,就终止了,然后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续开始执行,结果Thread-1在4毫秒内也执行结束了,Thread-1也结束了终止了。CPU在来回改变线程的执行机会称之为线程上下文切换。
public class Main {
public static void main(String[] args) {
// 一边吃饭一边喝酒
new EatThread().start();
new DrinkThread().start();
}
}
class EatThread extends Thread{
@Override
public void run() {
System.out.println("开始吃饭?...\t" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束吃饭?...\t" + new Date());
}
}
class DrinkThread extends Thread {
@Override
public void run() {
System.out.println("开始喝酒?️...\t" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束喝酒?...\t" + new Date());
}
}
并发编程,一边吃饭一边喝酒总共用时5秒,比顺序编程更快,因为并发编程可以同时运行,而不必等前面的代码运行完之后才允许后面的代码。
本示例主要启动3个线程,一个主线程main thread、一个吃饭线程(Thread-0)和一个喝酒线程(Thread-1),共三个线程, 三个线程并发切换着执行。main线程很快执行完,吃饭线程和喝酒线程会继续执行,直到所有线程(非守护线程)执行完毕,整个程序才会结束,main线程结束并不意味着整个程序结束。
顺序:代码从上而下按照固定的顺序执行,只有上一件事执行完毕,才能执行下一件事。就像物理电路中的串行,假如有十件事情,一个人来完成,这个人必须先做第一件事情,然后再做第二件事情,最后做第十件事情,按照顺序做。
并行:多个操作同时执行,他们之间是并行的。假如十件事情,两个人来完成,每个人在某个时间点各自做自己的事情,互不影响。
并发:将一个操作分割成多个部分执行并允许无序处理,假如有十件事情,如果有一个人在做,这个人可能做一会这个就不想做了,再去做别的,做着做着可能也不想做了,又去干其他事情了,看他心情想干哪个就干哪个,最终把十件事情都做完。如果有两个人再做,他们俩先分一下,比如张三做4件,李四做6件,他们各做自己的,在做自己的事情过程中可以随意的切换到别的事情,不一定要把某件事情干完再去干其他事情,有可能一件事做了N次才做完。
通常一台电脑只有一个CPU,多个线程属于并发执行,如果有多个CPU,多线程并发执行有可能变为并行执行。
多线程的创建方式
继承Thread
实现Runable
实现Callable
继承java.lang.Thread,重写run()方法
public class Main {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
Thread类
package java.lang;
public class Thread implements Runnable {
// 构造方法
public Thread(Runnable target);
public Thread(Runnable target, String name);
public synchronized void start();
}
实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装
Runnable接口
package java.lang