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

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和微信这就是并发,而对CPU1CPU2来说,其实是在并行执行任务,此时就可以理解并发和并行同时存在。

四、线程的基本使用

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

1、继承Thread类,重写run方法。

2、实现Runnable接口,重写run方法。

(二)线程的基本使用

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

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

  1. 1.public class project1 {    

  2. 2.    public static void main(String[] args) throws Exception{    

  3. 3.    

  4. 4.        //创建一个Cat对象,可以当作线程使用    

  5. 5.        Cat cat = new Cat();    

  6. 6.        cat.start();               //启动线程    

  7. 7.    }    

  8. 8.}    

  9. 9.    

  10. 10./**  

  11. 11. * 创建线程方法1:继承Thread类,重写run方法  

  12. 12. * 说明:  

  13. 13. * 1、当一个类继承了Thread类,该类就可以当作线程使用  

  14. 14. * 2、我们通过重写run方法,来实现自己的业务逻辑代码  

  15. 15. */    

  16. 16.class Cat extends Thread{    

  17. 17.    int times = 0;       //用于记录输出的次数  

  18. 18.    @Override    

  19. 19.    public void run() {//重写run方法,写上自己的业务代码    

  20. 20.        while(true){    

  21. 21.            //业务需求:该线程每间隔1秒,在控制台输出“Hello,我是小猫咪”    

  22. 22.            System.out.println("Hello,我是小猫咪" + (++times));    

  23. 23.    

  24. 24.            try {    

  25. 25.                //让该线程休眠1s    

  26. 26.                Thread.sleep(1000); //该方法的单位是ms(毫秒)    

  27. 27.            } catch (InterruptedException e) {    

  28. 28.                throw new RuntimeException(e);    

  29. 29.            }    

  30. 30.            if (times == 10){    

  31. 31.                break;          //当times 为10时,退出while循环,这时线程也就退出。。    

  32. 32.            }    

  33. 33.        }    

  34. 34.    }    

  35. 35.} 

运行结果如下:

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

1、什么情况下会用到通过实现Runnable接口的方式来创建线程呢?

说明:

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

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

具体代码如下:

  1. 1.public class project02 {    

  2. 2.    public static void main(String[] args) {    

  3. 3.        Dog dog = new Dog();    

  4. 4.        //dog.start();        //注意:因为通过实现Runnable接口的方式创建的Dog不能直接调用start方法    

  5. 5.        Thread thread = new Thread(dog);    

  6. 6.        thread.start();    

  7. 7.    }    

  8. 8.}    

  9. 9.    

  10. 10.class Dog implements Runnable{ //通过实现Runnable接口的方式,开发线程    

  11. 11.    int count = 0;    

  12. 12.    @Override    

  13. 13.    public void run() {    

  14. 14.        while (true){    

  15. 15.            System.out.println("小狗汪汪叫。。 " + ++count + "  线程的名称为:" + Thread.currentThread().getName());    

  16. 16.    

  17. 17.            //休眠1s    

  18. 18.            try {    

  19. 19.                Thread.sleep(1000);    

  20. 20.            } catch (InterruptedException e) {    

  21. 21.                throw new RuntimeException(e);    

  22. 22.            }    

  23. 23.    

  24. 24.            if (count == 10){    

  25. 25.                break;    

  26. 26.            }    

  27. 27.        }    

  28. 28.    }    

  29. 29.} 

2、运行结果如下:

(二)线程的启动流程

以上述(二)中猫咪程序为例进行讲解。

1、首先,你运行了这个程序,即执行了Run操作,就相对于你启动了这个进程

2、启动这个进程后,马上就会进入我们的主方法(main方法),也就是启动了主线程main线程)

3、启动主线程后,又在主线程中启动了另外一个线程,Thread-0线程(即通过stat()方法启动了Cat线程)

4、需要理解:主线程结束后,进程并不一定就结束了,因为主线程的子线程可能还没有结束,反正,主线程结束并且主线程的子线程也结束后,进程才会结束。


注意:

1main启动一个子线程 Thread-0,主线程不会阻塞(即不需要等你子线程的start()方法执行完成后才往下走),会继续执行

2换句话说:主线程在启动子线程后,主线程中子线程下面的代码也会继续执行

3即此时主线程和子线程中的代码同时在交替执行

  1. 1.public class project1 {    

  2. 2.    public static void main(String[] args) throws Exception{    

  3. 3.    

  4. 4.        //创建一个Cat对象,可以当作线程使用    

  5. 5.        Cat cat = new Cat();    

  6. 6.        cat.start();               //启动线程    

  7. 7.    

  8. 8.        //说明:当main启动一个子线程 Thread-0,主线程不会阻塞,会继续执行    

  9. 9.        //换句话说:主线程在启动子线程后,主线程中子线程下面的代码也会继续执行    

  10. 10.        //即此时主线程和子线程中的代码同时在交替执行    

  11. 11.        System.out.println("主线程继续执行:" + "主线程名称为:" + Thread.currentThread().getName());    

  12. 12.        for (int i = 0; i < 10; i++){    

  13. 13.            System.out.println("主线程 i= " + i);    

  14. 14.            //让主线程休眠1s    

  15. 15.            Thread.sleep(1000);    

  16. 16.        }    

  17. 17.    }    

  18. 18.}  

运行结果如下:

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

不知道小伙伴们在学习线程时候是否会有这样的疑问,线程的启动为什么使用的是start()方法,我们在创建线程时候,无论是使用继承Thread类的方式,还是实现Runnable接口的方式,都是重写的run方法,但是为什么在启动线程的时候是调用的start()方法,而不是run()方法呢?

不妨我们来做个验证,将上述代码案例改为通过调用run方法来创建线程,看看结果如何。

1、将上述代码进行修改,修改如下:

  1. 1.public class project1 {    

  2. 2.    public static void main(String[] args) throws Exception{    

  3. 3.    

  4. 4.        //创建一个Cat对象,可以当作线程使用    

  5. 5.        Cat cat = new Cat();    

  6. 6.     //   cat.start();               //启动线程    

  7. 7.        cat.run();                   //假设,我们现在通过调用run()方法,来启动线程(修改的地方)    

  8. 8.    

  9. 9.        //说明:当main启动一个子线程 Thread-0,主线程不会阻塞,会继续执行    

  10. 10.        //换句话说:主线程在启动子线程后,主线程中子线程下面的代码也会继续执行    

  11. 11.        //即此时主线程和子线程中的代码同时在交替执行    

  12. 12.        System.out.println("主线程继续执行:" + "主线程名称为:" + Thread.currentThread().getName());    

  13. 13.        for (int i = 0; i < 10; i++){    

  14. 14.            System.out.println("主线程 i= " + i);    

  15. 15.            //让主线程休眠1s    

  16. 16.            Thread.sleep(1000);    

  17. 17.        }    

  18. 18.    }    

  19. 19.}   

2、我们来看看输出结果:

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

结论:

为什么启动线程要使用start()方法,而不是通过调用重写的run()方法呢?

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


本篇文章来源于微信公众号: 温嘟嘟



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2023-05-28