🔥🔥🔥有关进程并发编程开发的成本问题
这次之前其实我们所有的写的程序都是使用单核心来运行的,但是一般我们的计算机都有很多核心,如果我们编程的时候,只使用一个核心的话,其实这是一个非常大的浪费,所以我们引进了多进程这种并发编程来充分利用计算机的多核心。但是随着我们的需求越来越大,进程的一些缺点就显现出来了。
- 创建和销毁进程的成本很大
- 越来越多的需求场景需要一种更方便的并发编程形式
- 线程的出现
由此我们发明了一个新的并发编程的形式,线程(Thread),对比进程,它的创建和销毁成本更小,更轻量。因此现在的主流并发编程的形式就是多线程编程。
🔥🔥🔥多线程编程
对于进程来说,一个进程是由一组PCB类似于结构体的形式对数据进行描述,进一步的通过链表的形式将数据组织起来。对于线程来说,一个线程是只有有一个PCB结构的形式,所以这里就有一个包含关系:一个进程中可以包含多个线程,此时每一个线程都可以独立的在CPU上调度执行,这里我们对进程和线程做一个总结:
- 进程是系统资源分配的基本单位
- 线程是cpu调度执行的基本单位
一个可执行的程序,双击exe文件,系统就会创建一个进程,给这个进程中分配一些系统资源(硬盘,cpu,内存,网络带宽等)。在这个进程中会创建一个或多个线程。这些线程才在cpu中调度执行。同一个进程中的多个线程其实是共用同一份系统资源的。
对比进程,线程是更轻量的,主要是在我们创建一个进程的时候,就已经创建了一个线程了,同时就会分配系统资源,后序我们在创建第二个第三个线程的时候,就不用再分配资源了~,因此他就省去了分配资源的过程。也省去了释放资源的过程。
🔥🔥🔥系统分析多线程调度执行过程
比如说现在有请我们最可爱的joey老铁上场,此时在一个房间里他要一个人吃完100只鸡。
为了提高老铁的吃鸡效率,我们可以引用多进程的概念,在创建一个房间,还是一个joey老铁,只需要吃50只鸡。
这样确实提高了joey吃鸡的效率,但是我们其实不难发现这种效率是非常低的,我们显然可以用一个更简单的方式提高吃鸡的效率,我们可以在一个房间里引进多个joey老铁,这样的方式不仅大大提高了吃鸡的效率,而且大大缩减了成本的损耗。这其实就是多线程的概念。
相比于多进程,这种形式效率是极高的。
那我们就自然的想到,在引进一些线程,会不会进一步提高效率呢?当然会!!!
但是当我们将线程增加带一些程度的时候,就会出现问题了~
此时就再也无法通过增加线程的方式,提高程序执行的效率了,因为桌子的空间是有限的,我们增加多个joey后,势必会有一些joey老铁是吃不到桌子上的鸡的,即线程多了,cpu的资源是有限的,这样就无法提高效率了。
有时甚至可能会由于线程的增多,导致线程可能会争夺系统资源,最后导致系统崩溃!!!
🔥🔥🔥线程和进程的概念即区别
1. 进程包含线程: 即一个进程中可以包含一个或者多个线程,但是不能没有线程,当我们创建一个main函数时就是一个主线程。
2. 进程是系统资源分配的基本单位,线程是系统调度执行的基本单位。
3. 同一个进程中的所有线程其实是共用同一份系统资源的(硬盘,内存,网络带宽等)
4. 线程是当下实现并发编程的主流方式,通过多线程可以很好的利用cpu的多核心特性~但是并不是线程越多越好的,随着线程的增多(当然创建线程也会有一定的开销),可能会导致多个线程之间争夺系统资源,甚至导致系统的崩溃
5. 多个线程之间,其实也存在一定的安全问题,当一个线程产生异常时,可能也会影响其他线程的正常执行~
6. 多个进程之间一般是不会相互影响的,、一个进程崩溃,并不会影响其他进程的运行(这也称为“进程的隔离性”)。
这个问题非常重要,是面试的高频问题,一定要牢记于心啊!!!
🔥🔥🔥如何在java中创建线程
线程其实是属于操作系统中的概念,操作系统会提供api,供程序员调用,但是不同的操作系统其实对应的api是不同的,java中jvm已经替我们将这些api封装好了,我们只需要关注jvm中的这一套api就好了。
一般我们通过使用Thread这个类来完成多线程的开发。
🗼🗼🗼继承Thread重写Run方法创建线程
class MyThread extends Thread{
@Override
public void run() {
System.out.println("Thread hello");
}
}
public class Demo2 {
public static void main(String[] args) {
MyThread t=new MyThread();
t.start();
System.out.println("main hello");
}
}
这里的run函数的主体其实就是我们完成线程主体任务的地方,在这里实现代码的主体部分,然后通过start方法,真正在内存中创建一个线程,通过回调函数run执行相关操作。回调函数就是用户手动定义了,但是没有手动调用,但是最后被系统调用的函数。
🗼🗼🗼实现Runnable创建线程
class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("hello thread");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
}
}
}
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable=new MyRunnable();
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
这里的sleep是Thread中的库函数,可以不导入包直接调用,但是这里存在一个受查异常,所以要进行异常捕捉。由于父类中的run方法并没有这个异常所以我们只能利用try-catch,而不能把异常向上抛出。
🗼🗼🗼继承Thread利用匿名内部类实现一个线程
public static void main(String[] args) throws InterruptedException{
Thread t=new Thread(){
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
匿名内部类定义在一个类的内部,只能使用一次。
🗼🗼🗼实现Runnable利用匿名内部类创建线程
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
🗼🗼🗼利用lamda表达式创建一个线程
public static void main(String[] args) throws InterruptedException{
Thread t=new Thread(()->{
while(true){
System.out.println("thread hello");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
}
},"自定义线程");
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
好了,今天就分享这么多了,感兴趣的话关注不迷路哦