什么是进程、线程,什么是并发、并行及线程的创建和线程的基本使用

一、什么是程序、进程、线程

1、什么是程序
程序可以理解为是我们执行的一段代码,是一种静态的概念

2、什么是进程
进程是指运行中的程序,是一个动态的概念。进程有它自身的产生、存在和消亡的过程(进程产生就会占用内存空间,反之如果进程消亡则会释放内存空间)
比如:我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间;又或是,当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

3、什么是线程
线程由进程创建的,是进程的一个实体
一个进程可以拥有多个线程
比如:你可以使用迅雷同时下载三个文件,这时就可以理解有三个线程在同时干活

二、单线程、多线程

1、什么是单线程
单线程:同一时刻,只允许执行一个线程。

2、什么是多线程
多线程:同一时刻,可以执行多个线程。
比如:一个qq进程,可以同时打开多个聊天窗口;又或是,一个迅雷进程,可以同时下载多个文件

三、并发、并行

1、什么是并发
并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉。简单的说,单个CPU实现的多任务就是并发。
比如:人在开车时候打电话,其实是你的大脑在同一时刻,对开车和打电话这两个任务交替执行的;又或是,假设你的电脑是单核CPU,但同时需要执行QQ、迅雷两个软件(进程),实际上,在同一时刻只能执行一个进程,如果要执行多个进程的话,就需要不停的切换,多个进程交替执行,看上去就像两个或多个进程同时在工作。

2、什么是并行
并行:同一时刻,多个任务同时执行。多核CPU可以实现并行。
比如:我的电脑有两个CPU,那么在同一时刻,一个CPU可以执行QQ这个进程,另一个CPU可以执行迅雷这个进程,同一时刻,多个任务可同时执行

3、并发和并行可能同时存在
比如说,你的电脑是两核CPU,而你想同时执行QQ、迅雷、微信这三个进程,那么假设其中一个CPU同一时刻交替执行QQ和微信,而另外一个CPU则执行迅雷,那么对于CPU1来说某一时刻交替执行QQ和微信这就是并发,而对CPU1和CPU2来说,其实是在并行执行任务,此时就可以理解并发和并行同时存在。

四、线程的基本使用

(一)创建线程的两种方式:

1、继承Thread类,重写run方法。
2、实现Runnable接口,重写run方法。
在这里插入图片描述

(二)线程基本使用

方式一:通过继承Thread类,重写run方法

编写程序,开启一个线程,该线程每间隔1s在控制台输出“Hello,我是小猫咪”,且当输出10次后结束该线程。

1.public class project1 {
    
      
2.    public static void main(String[] args) throws Exception{
    
      
3.  
4.        //创建一个Cat对象,可以当作线程使用  
5.        Cat cat = new Cat();  
6.        cat.start();               //启动线程  
7.    }  
8.}  
9.  
10./** 
11. * 创建线程方法1:继承Thread类,重写run方法 
12. * 说明: 
13. * 1、当一个类继承了Thread类,该类就可以当作线程使用 
14. * 2、我们通过重写run方法,来实现自己的业务逻辑代码 
15. */  
16.class Cat extends Thread{
    
      
17.    int times = 0;       //用于记录输出的次数
18.    @Override  
19.    public void run() {
    
    //重写run方法,写上自己的业务代码  
20.        while(true){
    
      
21.            //业务需求:该线程每间隔1秒,在控制台输出“Hello,我是小猫咪”  
22.            System.out.println("Hello,我是小猫咪" + (++times));  
23.  
24.            try {
    
      
25.                //让该线程休眠1s  
26.                Thread.sleep(1000); //该方法的单位是ms(毫秒)  
27.            } catch (InterruptedException e) {
    
      
28.                throw new RuntimeException(e);  
29.            }  
30.            if (times == 10){
    
      
31.                break;          //当times 为10时,退出while循环,这时线程也就退出。。  
32.            }  
33.        }  
34.    }  
} 

运行结果,如下:
在这里插入图片描述

方式二:通过实现Runnable接口的方式,重写run方法

1、什么情况下会用到通过实现Runnable接口的方式来创建线程呢?
说明:
因为Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然是不可能的了。因此引入了另外一种创建线程的方式,就是通过实现Runnable接口的方式来创建线程。

2、编写一个程序,该程序可以每间隔1s,在控制台输出”小狗汪汪叫。。。”,输出10次后,自动退出,请使用实现Runnable接口的方式,创建线程实现。
具体代码如下:

public class project02 {
    
      
    public static void main(String[] args) {
    
      
        Dog dog = new Dog();  
        //dog.start();        //注意:因为通过实现Runnable接口的方式创建的Dog不能直接调用start方法  
        Thread thread = new Thread(dog);  
        thread.start();  
    }  
}  
  
class Dog implements Runnable{
    
     //通过实现Runnable接口的方式,开发线程  
    int count = 0;  
    @Override  
    public void run() {
    
      
        while (true){
    
      
            System.out.println("小狗汪汪叫。。 " + ++count + "  线程的名称为:" + Thread.currentThread().getName());  
  
            //休眠1s  
            try {
    
      
                Thread.sleep(1000);  
            } catch (InterruptedException e) {
    
      
                throw new RuntimeException(e);  
            }  
  
            if (count == 10){
    
      
                break;  
            }  
        }  
    }  
} 

3、运行结果如下:

在这里插入图片描述

(三)线程的启动流程

以上述(二)中猫咪程序为例进行讲解。
在这里插入图片描述

1、首先,你运行了这个程序,即执行了Run操作,就相对于你启动了这个进程
2、启动这个进程后,马上就会进入我们的主方法(main方法),也就是启动了主线程(main线程)
3、启动主线程后,又在主线程中启动了另外一个线程,Thread-0线程(即通过stat()方法启动了Cat线程)
4、需要理解:主线程结束后,进程并不一定就结束了,因为主线程的子线程可能还没有结束,反正,主线程结束并且主线程的子线程也结束后,进程才会结束。
注意
1、当main启动一个子线程 Thread-0,主线程不会阻塞(即不需要等你子线程的start()方法执行完成后才往下走),会继续执行。
2、换句话说:主线程在启动子线程后,主线程中子线程下面的代码也会继续执行
3、即此时主线程和子线程中的代码同时在交替执行
举例代码如下:

public class project1 {
    
      
    public static void main(String[] args) throws Exception{
    
      
  
        //创建一个Cat对象,可以当作线程使用  
        Cat cat = new Cat();  
        cat.start();               //启动线程  
  
        //说明:当main启动一个子线程 Thread-0,主线程不会阻塞,会继续执行  
        //换句话说:主线程在启动子线程后,主线程中子线程下面的代码也会继续执行  
        //即此时主线程和子线程中的代码同时在交替执行  
        System.out.println("主线程继续执行:" + "主线程名称为:" + Thread.currentThread().getName());  
        for (int i = 0; i < 10; i++){
    
      
            System.out.println("主线程 i= " + i);  
            //让主线程休眠1s  
            Thread.sleep(1000);  
        }  
    }  
}

运行结果如下:
在这里插入图片描述

(四)线程启动为什么使用的是start()方法

不知道小伙伴们在学习线程时候是否会有这样的疑问,线程的启动为什么使用的是start()方法,我们在创建线程时候,无论是使用继承Thread类的方式,还是实现Runnable接口的方式,都是重写的run方法,但是为什么在启动线程的时候是调用的start()方法,而不是run()方法呢?
不妨我们来做个验证,将上述代码案例改为通过调用run方法来创建线程,看看结果如何。
1、将上述代码进行修改,修改如下:

public class project1 {
    
      
    public static void main(String[] args) throws Exception{
    
      
  
        //创建一个Cat对象,可以当作线程使用  
        Cat cat = new Cat();  
     //   cat.start();               //启动线程  
        cat.run();                   //假设,我们现在通过调用run()方法,来启动线程(修改的地方)  
  
        //说明:当main启动一个子线程 Thread-0,主线程不会阻塞,会继续执行  
        //换句话说:主线程在启动子线程后,主线程中子线程下面的代码也会继续执行  
        //即此时主线程和子线程中的代码同时在交替执行  
        System.out.println("主线程继续执行:" + "主线程名称为:" + Thread.currentThread().getName());  
        for (int i = 0; i < 10; i++){
    
      
            System.out.println("主线程 i= " + i);  
            //让主线程休眠1s  
            Thread.sleep(1000);  
        }  
    }  
}  

2、我们来看看输出结果:
在这里插入图片描述

此时此刻,根据输出结果我们会发现,这段代码先是输出了主函数中调用的cat.run()对应的执行结果,等到cat.run()这个函数执行完成后,才会继续执行主函数中cat.run()方法下面的语句。

结论:
为什么启动线程要使用start()方法,而不是通过调用重写的run()方法呢?
因为使用cat.run()方法,其实并非是真正的启动线程,而仅仅是调用了重写的run()方法,只有等待run()方法执行完成后才能执行其主函数的其他语句,而实质并非是达到了启动线程的效果,故启动线程需要使用start()方法。

猜你喜欢

转载自blog.csdn.net/weixin_43950588/article/details/128523382