那个网站做生鲜,宁夏网站开发设计说明书, 上色软件免费直播间,外链工具软件一、线程的引入
上节#xff0c;我们介绍了进程的概念#xff0c;以及操作系统内核是如何管理进程的#xff08;描述组织#xff09;#xff0c;PCB中的核心属性有哪些#xff0c;
引入进程这个概念#xff0c;最主要的目的#xff0c;就是为了解决“并发编程”这样的…一、线程的引入
上节我们介绍了进程的概念以及操作系统内核是如何管理进程的描述组织PCB中的核心属性有哪些
引入进程这个概念最主要的目的就是为了解决“并发编程”这样的问题。
为什么我们需要并发编程
这是因为CPU进入了多核心时代为了进一步提高程序的执行速度就需要充分利用CPU的多核资源这就需要“并发编程”。
多进程编程让大量进程可以在多个CPU核心上运行程序代码已经能够把CPU的多核资源利用起来了已经可以解决“并发编程”的问题了。
我们为什么又要学习多线程编程呢
线程又是什么呢和进程又有什么关系
带着这些疑问我慢慢进行介绍。
二、线程和进程的关系 1进程包含线程。一个进程可以包含一个线程也可以包含多个线程。进程中至少有一个线程不能没有。 2一个线程是通过一个PCB来描述的PCB对应的是线程一个线程对应一个PCB一个进程对应1个或多个PCB。 3进程是操作系统资源分配的基本单位线程是操作系统调度执行的基本单位。同一进程里的多个线程之间共享进程资源。 PCB中的核心属性pid、内存指针、文件描述符表这些是进程中的线程共用的。处于同一个进程中的线程pid相同内存指针和文件描述符表也是一样的。 PCB中与调度相关的属性状态、优先级、上下文、记账信息是每个线程自己有自己的各自记录各自的。 对于第三点我再啰嗦几句希望能加深大家的理解
同一个进程里的多个线程之间共用了进程的同一份资源。这里共用的资源主要指的是内存指针和文件描述符表。比如线程1里new的对象在线程2,3,4里都可以直接使用共用内存指针线程1打开的文件在线程2,3,4里都可以直接使用共用文件描述符表。
操作系统实际调度的时候是以线程为单位进行调度的。并不关系进程只关心线程。谈到调度就和进程无关了上节提到的进程调度指的是这里的进程只包含一个线程的情况。如果进程中有多个线程那么每个线程是独立在CPU上调度的。比如线程1可能在核心A上执行线程2可能在核心B上执行。线程是操作系统调度执行的基本单位。每个线程都有自己的执行逻辑我们称为执行流。
三、为什么要使用多线程编程
多进程和多线程都可以解决并发编程问题为什么更倾向于使用多线程编程呢
因为
进程太“重”了。创建/销毁/调度一个进程都需要很大的资源消耗速度慢。
线程又叫“轻量级进程”。创建/销毁/调度一个线程消耗资源少速度快。
使用多线程编程会提高效率。
为什么线程比较“轻”
因为只有在创建第一个线程时操作系统会进行资源分配主要指内存指针文件描述符表之后创建的第2,3,4...个线程复用之前的分配的资源基本不需要操作系统再分配资源。销毁一个线程也基本不需要释放资源。除非这个进程中只有这一个线程了才需要释放资源。
而进程就不一样了创建一个进程操作系统会进行资源分配销毁一个进程要释放资源。创建第二个进程还需要申请资源销毁第二个进进程同样需要释放资源。消耗资源多速度慢。
因此由于进程和线程各自的特点我们一般使用多线程编程减少资源消耗提高速度。
四、线程越多越好多线程会有安全问题吗
增加线程数量并不是可以一直提高速度。 CPU的核心数量是有限的。线程太多核心数目有限不少的开销反而浪费在线程调度上。所以并不是线程越多越好。
多线程容易出现线程安全问题。 1多线程中共享同一份资源可能会出现多个线程同时都需要同一个资源的现象如同一个变量出现争抢 2如果一个线程抛异常处理不好的话可能会把整个进程都带走其它线程也就挂了。 什么时候会出现安全问题多个执行流访问同一个共享资源的时候。
线程模型天然就是资源共享的多线程争抢同一个资源如同一个变量非常容易触发。
进程模型天然就是资源隔离的不容易触发。只有进行进程间通信时多个进程访问同一个资源这时才可能会出问题。
也就是说多线程会提高效率但是不如多进程安全。当然代码写的靠谱线程安全问题也不怕
五、在Java中如何进行多线程编程
操作系统提供了操作线程的一系列API。
而Java是一个跨平台的语言很多操作系统提供的功能都被JVM给封装好了。我们不需要学习操作系统提供的原生API只需要学习Java提供的API就行啦。
Java操作多线程最核心的类是Thread在java.lang下不用import
创建线程是希望线程成为一个独立的执行流能执行一段代码。我们可以这样理解
创建线程就相当于雇了个人来干活我们得告诉他要干啥活他才能去执行。
如何告诉他要干啥活呢
我们有如下方法java中创建线程的写法有很多种如下所示
继承Thread重写run方法实现Runnable接口使用匿名内部类继承Thread使用匿名内部类实现Runnable接口使用Lambda表达式
下面分别进行介绍
1、继承Thread重写run方法
class MyThread extends Thread{Overridepublic void run() {System.out.println(hello thread);}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t new MyThread();t.start();}
}t.start();
start方法是线程中的一个特殊方法作用是创建一个线程。
在上面代码中start这个方法创建了一个新的线程新的线程负责执行run方法并不是start方法里面调用了run方法是新的线程调用的run方法。当run方法执行完时新的线程自然销毁。
那么start是如何创建一个新线程的 调用操作系统提供的API系统调用通过操作系统内核创建新线程的PCB并且把要执行的指令交给这个PCB当PCB被调度到CPU上执行的时候也就执行到了线程run方法中的代码了。 在main方法中直接打印hello world和在main方法中调用start方法的上述做法有啥区别 如果只是在main方法中直接打印hello world这个java进程就只有一个线程调用main方法的线程也就是主线程。 在main方法中调用t.start()是主线程调用start方法创建出一个新的线程新的线程调用run方法执行其中的代码。这个java进程中有两个线程。 如果把 t.start(); 改成 t.run(); 有什么区别吗 有很大区别。 是 t.run(); 的话这个java进程中还是只有一个主线程。所有的活都是主线程一个人干的。因为new Thread对象的操作并不创建线程只有调用了start方法才是真正创建了PCB才真正有个货真价实的线程。 2、实现Runnable接口
解耦合目的是让线程和线程要干的活之间分离开。
未来如果要改代码不用多线程了使用多进程或者线程池协程......此时代码改动比较小。
//Runnable 作用描述一个”要执行的任务“ run方法就是执行任务的细节
class MyRunnable implements Runnable{Overridepublic void run() {System.out.println(hello world);}
}
public class ThreadDemo2 {public static void main(String[] args) {//这只是描述了个任务就是线程要干的活Runnable runnable new MyRunnable();//把任务交给线程来执行Thread t new Thread(runnable);t.start();}
}3、使用匿名内部类继承Thread
public class ThreadDemo3 {public static void main(String[] args) {Thread t new Thread(){Overridepublic void run() {System.out.println(hello world);}};t.start();}
} 红框框里是一个匿名内部类对象这里做了两件事 1、创建了一个Thread的子类继承Thread类子类没有名字所以才叫“匿名”。 2、对子类进行实例化new让 t 引用指向该实例。 4、使用匿名内部类实现Runnable接口
public class ThreadDemo4 {public static void main(String[] args) {Thread t new Thread(new Runnable() {Overridepublic void run() {System.out.println(hello world);}});t.start();}
} 5、使用Lambda表达式
直接把 lambda 传给 Thread 的构造方法
lambda 就是个匿名方法
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread(() - {System.out.println(hello world);});t.start();}
}