zoukankan      html  css  js  c++  java
  • 并发编程 -- 多线程

    并发编程 -- 多线程

    进程

    在理解多线程之前,我们先需要了解什么是进程?

    进程说白了就是在你的内存空间中开辟出的一个独立的空间;

    如果还不理解的话,我再解释一下;

    想必各位之前都安装过软件吧,你安装完软件之后,在你的软件安装包里面是不是有一个.exe文件,那你双击exe文件的时候,在你的任务管理器,在里面就有一个进程选项卡,就是说,每当你打开一个exe文件的时候,它都会显示在任务管理器的进程当中,所以就可以把运行中的任意一款软件,都可以把它看做一个进程;

    当然,以上的操作方式是在windows系统的操作的,也就是说,想查看Windows的进程,只需要在任务管理器中查看即可;

    在linux下使用命令 ps 或 pstree、ps -eflgrep;

    如果想在linux系统下查看java的相关进程,命令为:jps;

    那么问题就来了,当你打开QQ的时候,是不是就是开启了一个进程,当你开始使用它并且聊天的时候,比如你是a,你现在要跟b聊天然后再去跟c聊天,那么这样的操作是不是相互独立的呢?也就是说,你现在要跟b发送你的游戏密码,这个时候c问你晚上吃啥饭,你发的密码c知道吗?肯定不会啦,所以你跟b聊天的时候,是不会影响你跟c聊天的,因为你跟b c 是相互独立的

    那么,在这个里面,你跟他们每个人产生的通话底层是怎么实现的呢?

    底层就是靠线程去实现的;

    线程

    什么是线程呢?

    线程是指程序在执行过程中,能够执行程序代码的一个执行单元,在Java语言中,线程有四种状态:运行,就绪,挂起,结束。一般情况下,一个操作系统是有多个进程,那么每个进程都要对应多个线程;

    线程与进程有区别吗?

    有,进程是一段正在运行的程序,而线程有时也被称为轻量级进程,它是进程的执行单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间,但是,各个线程拥有自己的栈空间。

    一个进程可以有很多线程,每条线程并行执行不同的任务;

    实现单个线程

    在之前的一些简单的java练习中,我们运行的时候,是不是都是在main方法中测试运行啊,那么,在这之前,我仅仅编写了一些非常简单的java代码,甚至就在main方法中输出了一句话,就可以直接完成运行,在这期间,我并没有创建有关线程方面的方法以及程序,那么就怎么实现运行了呢?

    其实很简单,Main方法既然能运行你的程序,那么必然就会有一个线程,那么这个线程就是单线程,那,我们如何查看本次运行线程的线程名呢?

    我们仅需在main方法里面输出以下即可:

    System.out.println(Thread.currentThread());

    打印出来后,我们就可以看到线程名师Main,因为就一个线程,所以main就是主线程;

    那么就一个线程,就表明,在这之前,我们所做的一些练习程序都是单线程的;

    线程的创建方式

    两种:

    1.继承(extends) Thread

    继承完Thread类之后需要重写run方法;

    2.实现(implem) Runnable接口

    也许需要重写run方法;

    继承Thread类创建线程

    讲了那么多,那么就开始上手实操一下,我们将要练习的是继承Thread来创建一个线程;

    首先,我们创建一个类(MyThread)然后继承Thread类;

    继承完Thread后,实现run方法;

    run方法的作用就是,相当于这个线程对用户提供的一个接口;

    所以,用户有什么业务逻辑,都需要写在run方法里面:

    复制代码
    public class MyThread extends Thread{
    
        public void run(){
    
                      //作用:相当于这个线程向用户提供的接口,用户有什么样的业务逻辑,写在这个方法中
    
        }
    
    }
    复制代码

    那我们现在就让这个run方法就实现一个简单的打印;

    这个就是我使用了第一种方式来创建了一个线程;

    那么,你这个线程创建完成后,如果你想调用它怎么办?

    调用线程

    我再建一个类(TestThread);

    然后提供一个Main方法;

    在main方法中创建一个线程,语法:

    //创建一个线程
    
    MyThread mt = new MyThred();

    这个就是创建线程,创建完后,下一步我们需要调用start方法;

    复制代码
    public class TestThread{
    
    public static void main (String [] args){
    
    //创建线程
    
    MyThread mt = new MyThred();
    
    //启动线程
    
    mt.start();
    
           }
    
       }
    复制代码

    为什么要调用start,而不是run?

    其实很简单,mt.start就是启动你的线程,那么启动后,它底层就会去调用你的run方法;

    这个就是线程的启动式start方法;

    我们运行一下后,看一下控制台打印:

    就是一些输出,感觉就跟调用方法一样,对吧;

    那么,接下来,我们看一下第二种创建方式

    实现(implem) Runnable接口创建线程

    这种方法跟上面的那种,大同小异,只不过上面那个是继承,这个是实现接口;

    实现Runnable接口后,需要实现它的run方法;

    复制代码
    public class MyRunnableThread implements Runable{
    
        public void run(){
    
                      //作用:相当于这个线程向用户提供的接口,用户有什么样的业务逻辑,写在这个方法中
    
        }
    
    }
    复制代码

    现在,我们用第二种方式创建了一个线程,当然,业务逻辑跟上面的那个相同,因为举例,所以没有深究别的;

    第二种方式调用线程就有所不同

     因为运行线程永远需要Thread里面的start方法来启动线程,所以需要把Thread创建出来,再将创建出来的线程放进去;

    所以打印出来的结果是跟上面的结果是一样的,这里就不再放图上去了;

    线程关键字分析

    start,是线程启动的方法;

    run方法是线程执行过程中调用的方法(默认调用),在上面的例子我们也看到了,你并没有手动去调用run方法,是他自动调用的,就跟你创建对象的时候,默认调用构造方法一样;

    深究run与start

    那,启动线程一定是要用staet方法启动,我试试不用它,我直接调用Thread中的run方法可行吗?

    可行因为抛开线程,你本身就是实例化了Thread这个类,并调用该类中的run方法是没有问题的,但是,不纳入线程中!!

    我们直接调用run方法后,发现,方法可以正常打印,因为,仅仅完成了普通方法的调用,实际上并没有启动线程;

    多线程底层执行原理

    说道底层运行,那么是不是就是需要依靠CPU啊;

    那,各位之前有没有听过一句话叫做,一个CPU在同一个时间片只能执行一个程序

    什么意思呢?

    就是,你的程序是不是都运行在一个CPU上啊,那你真正一个CPU在同一个时间片里是不是只能执行一个程序呀,那这个程序究竟要执行那个程序,是不是就需要通过线程之间时间片的一个争抢;

    时间片:微小的时间段;

    多线程说白了就是时间片的争夺,那个线程获取了时间片,就执行那个线程的代码;

    假设,t1线程先获得时间片,那么,t1线程就优先执行;

    但是,它不可能拿着那个时间片不放吧,因为在CPU执行的过程中,底层运用轮循制的;

    多线程执行的时候,CPU分配时间片是采取轮循的方式进行分配的;

    就是轮流,有点像值日的时候,轮流值日一样;

    那,CPU在分配时间片的时候,第一个t1先抢占到了之后,他先执行了一段时间之后,CPU把这个t1执行完了以后,CPU是不是接着把时间片分配给t2去执行了;

    那事实上,也是t2也在去抢占时间片;

    t1执行完毕后,那么,CPU就将迎来新的一轮争夺,这个时候t2抢到了,就开始执行t2的代码;

    这就是多线程的底层执行原理;

    多线程它在本质上运行的时候,他是同时执行的,还是轮流执行的呢?

    肯定不是同时执行的,也就是不是我们常说的并发执行;那在你们看来,实际就是宏观上你来看就是同时执行,但是在微观上是不是的;

    线程的状态

    线程总共有五种状态;

    第一个状态 新建状态

    新建状态,就是你新建一个线程是的状态,也就是你新建了一个线程但还没有启动时的状态;

    当线程执行start方法的时候,就会进入就绪状态;

    第二个状态 就绪状态

    进入就绪状态的时候,事实上就是准备抢占CPU的时间片;

    一旦抢占到了CPU的时间片它就会立即进入运行状态;

    第三个状态 运行状态

    当线程抢占到了CPU时间片的时候,它才会运行,所以第三个状态是,运行状态;

    在它的运行状态中,还有可能执行一个代码,Throad.sleep();睡眠;

    就是在你执行的时候,突然让你睡眠了,我都让你这个线程睡眠了,你还有必要去争夺这个CPU资源吗?

    就肯定没有必要再去争夺这个CPU资源了,那这个时候你就需要释放CPU啊,对不对,你释放之后,你下次再运行的时候,你就需要重新获取CPU的时间片,所以这种状态就叫做堵塞状态;

    第四个状态 堵塞状态与sleep方法

    想让线程阻塞,最常用的方式就是使用sleep,用sleep这个方法,可以使运行中的线程回到就绪状态;

    因为它需要重新抢占CPU资源的,所以,sleep状态的最终目的是让改线程回到就读状态;

    就比如,我现在想让这个线程,进我想让它每次进入run方法中的for循环打印里写一个睡眠,一遍循环遍历输出,一边睡眠看会发生什么:

    我在run方法中业务写完后,我们测试一下该线程:

    在上图中,可以发现,我同时调用了两次start方法,说明,我执行了两次我一次性开启了两次线程,并且执行了两次,我们看看会不会出现交替执行的情况:

    从输出结果来看,确实交替执行了并且,是俩俩执行的:

    每过一秒,就会执行一次:

    我就不继续演示了;

    所以,我们从中可以看出,不管哪个线程过来,t1也好t2也好,执行的时候,均睡眠一秒钟,睡眠完一秒钟之后,谁先醒了,谁就继续向下执行,这个就是到点自然醒的;

    也可以使用join来造成线程堵塞;

    join

    刚刚,我们在上面介绍了sleep,我们来看看join;

    join():是线程加入

    它底层执行的是,当你在执行一个线程的时候,如果遇到其他线程加入,则会先执行加入的线程,直到的加入的线程执行完成,才会继续执行原来线程的任务;

    什么意思呢?

    就是说,还是上面那个t1,与t2的例子,那假设说,t1在执行的过程中,突然遇到了一个代码t2.join,这时候,就会在这个时间片,优先执行t2的线程;

    join()方法可以给一个参数,参数代表执行的毫秒; 

    第五个状态 死亡状态

    线程执行完了,或因异常退出了,都会结束生命周期,这就是死亡状态;

  • 相关阅读:
    生产者消费者问题 一个生产者 两个消费者 4个缓冲区 生产10个产品
    三个线程交替数数 数到100
    c++ 字符串去重
    Java中一个方法只被一个线程调用一次
    GEF开发eclipse插件,多页编辑器实现delete功能
    python-arp 被动信息收集
    ssrf
    TCP
    xxe
    越权
  • 原文地址:https://www.cnblogs.com/Children-qiuzhen/p/10901332.html
Copyright © 2011-2022 走看看