前言
到目前为止,我们学到的都是有关 “顺序” 编程的知识,即程序中所有事物在任意时刻都只能执行一个步骤。例如:在我们的 main 方法中,都是多个操作以 “从上至下” 的顺序调用方法以至结束的。
虽然 “顺序” 编程能够解决相当大的一部分问题,但是当所有的方法全部推在一个 “队列” 里面,如果前面的方法没有执行完,后面的方法就轮不到。这样程序的运行效率必然十分低下。对于一些问题,能够同时执行程序的多个部分,就会十分方便且必要。而并发编程的设计就是为了专门解决这一类问题。
前期回顾:Java 之深入理解 String、StringBuilder、StringBuffer
文章代码:码云地址
目录
前言
并发与并行的简单区别
并发
总结
并行
总结
进程与线程的简单区别
进程
线程
进程与线程的区别总结
多线程的理解
创建线程任务的方式
继承 Thread 类
实现 Runnable 接⼝
匿名内部类创建
lambda 表达式创建
并发与并行的简单区别
并发
关于并发这里举个例子:一家餐厅来了三个食客,但是厨师只有一位,怎么出餐就成了问题。<1> 按食客进店的先后顺序上(之前提到的顺序编程),那么后两位食客等的时间久了自然不乐意肯定会掀桌子。<2> 三个人的菜同时做,虽然只有一个厨师(一个CPU核心),但是有三口锅(三个线程),当一个菜烧制的差不多的时候立刻就切换到另一个菜。这样几乎可以做到三个菜的同时出餐。
在计算机中,由于 CPU 的运算速度非常的快,切换的速度也非常的快,如上图,可能执行到QQ的指令2就立刻跳转到微信的线程,站在宏观角度,可以看成是同时执行(并发执行)的。
总结
并发是指两个或多个任务在同一时间间隔内发生。比如在单核CPU上运行 16 个线程,由于核数限制,这 16 个线程无法在同一时刻运行,所以CPU只能采用时间片切换的方式来运行。
并行
关于并行这里举个例子:一家餐厅来了三个食客,但是厨师刚好有三位。这样怎么出餐就不成问题了。我们只需每桌安排一个厨师,就可完成出餐任务。
在计算机中,这就意味着有一个CPU有多个核心,同一时刻的程序可以分配到多个CPU处理器上,实现多任务,并行执行。
总结
当有多个CPU核心时,在同一个时刻可以同时运行多个任务,这种方式叫并行。
比如,3核CPU可以同时运行3个线程。
顺序编程是指多个操作按次序执行,并行编程是指多个操作同时执行,而并发编程将同一个操作分解为多个部分并允许无序执行。
进程与线程的简单区别
进程
进程是指正在运行中的程序实例。每个进程都是一个独立的执行单元,拥有自己的内存空间和系统资源。一个进程可以包含多个线程,是程序执行的基本单位一个在内存中运行的应用程序。比如在 Windows 系统中,一个运行的 xx.exe 就是一个进程。我们打开任务管理器就可以看到此时电脑的进程。
线程
线程是进程内可计划执行的实体。 进程的所有线程共享其虚拟地址空间和系统资源。一个进程至少有一个线程,即主线程 main;一个进程可以运行多个线程,多个线程可共享数据。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
进程与线程的区别总结
线程具有许多传统进程所具有的特征,故又称为轻型进程或进程元;而把传统的进程称为重型进程,它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。 资源开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。
多线程的理解
这里先举个例子 ~
单进程
猴子吃桃, 一只猴子在一间包间内需要吃一百个桃,我们知道这其实效率是很低的。那么我们可以将猴子吃桃当成是一个进程处理一个大型项目,那么这样搞会导致程序运行卡顿。所以我们需要将其优化。
多进程
我们可以将吃完 100 颗桃子的任务分给4子猴子完成,虽然效率提升了,但是每只猴子都需要开一间包间吃桃子,这就导致了资源的开销大。将一个程序交给4个进程也是一样的道理。
多线程
那么我在一个包间让多个猴子完成吃完桃子的计划,这样的安排是最合理的。既保证了效率,也节省了空间。那么猴子是不是越多效率越快呢?其实并不然,因为座位有限,当座位无法容纳更多的猴子,此时再添加猴子只会让它们相互 “争夺” 吃桃子的位置,从而影响吃桃的效率。
多线程也是如此:一个进程中可以拥有很多线程来辅助程序的运行,这里提升效率的因素是 “并行”。但是如果线程太多超过了CPU的核心数目,此时就无法完成所有线程的并行执行,就会势必会造成严重的 “线程冲突”。
创建线程任务的方式
继承 Thread 类
线程可以驱动任务,因此你需要一种描述任务的方式。这可以继承 Thread 这个线程类。要想定义任务,我们只需要重写 Thread 类中的 run() 方法即可,使得该任务执行你的命令:
class MyThread extends Thread{
@Override
public void run() {
while(true){
// 即将创建出的线程,要执行的逻辑
System.out.println("Hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1{
public static void main(String[] args) {
MyThread thread = new MyThread();
// 创建线程
thread.start();
while(true){
System.out.println("World");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
运行截图:
<1> 任务中的 run() 方法通常总会以某种方式的循环,使得任务一直执行下去直到不需要,所以需要设置循环条件。通常 run() 会被写成无限循环形式,这就意味着,除非有某个条件使得 run() 停止,否则它将会永远的运行下去。
<2> 由于该程序是死循环,为了更好的观察加上了sleep。而我们要捕获 sleep 方法有可能会抛出 InterruptedException 异常。一般来说,中断请求终止一个线程。相应的,如果抛出 InterruptedException 时,run() 方法就会退出。
<3>
World
Hello
Hello
World
Hello
World
Hello
World
...
通过打印结果我们可以发现,这个线程的输出是交错的,说明它们在并发的进行。
<4> 以上的程序一共有两个线程,一个是 main 线程,一个是我们用 Thread 创建的线程。但是如果我们直接调用 Thread 的 run() 的方法,只会在 main 线程中执行这个方法,而没有开辟新的线程。
<5> 虽然以上方法可以创建线程但是不推荐使用,我们应该将并行运行的任务与运行机制解耦合。如果有多个任务,为每个任务分别创建一个线程开销太大。
实现 Runnable 接⼝
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程需要完成的逻辑
while(true){
System.out.println("Hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
}
public class Demo2 {
public static void main(String[] args) {
// 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为参数
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
while (true) {
System.out.println("World");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
匿名内部类创建
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run(){
while(true){
System.out.println("Hello World");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t.start();
}
lambda 表达式创建
public static void main(String[] args) {
Thread t = new Thread(()->{
while(true){
System.out.println("Hello World");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}