1、多线程

1、概念        

1.1、概念    

       要说明多线程,我们先来了解一下程序、进程、线程,如图所示——计算机的组成

        我们举例说明,比如QQ,当我们启动QQ时,程序会从磁盘通过IO进入到内存, 变成一个进程;程序开始执行时,是以线程为单位,操作系统会找到主线程main方法,然后交给CPU执行,而主线程开启了其他线程,则在线程间来回切换,如A线程执行一会儿,然后等待下来,让B线程执行一会儿,然后再让A线程执行,如此来回切换。这么看来,进程里面可以有多个线程,我们下面详细说明他们的关系。

程序:Program,是一个指令的集合

进程:Process,正在执行的程序,是一个静态的概念,是资源分配的单位

           (1)进程时程序的一次静态态执行过程,占用特定的地址空间

           (2)每个进程都是独立的,由3部分组成,CPU、data、code

           (3)它的缺点是内存浪费,给cpu带来负担

线程:是进程中一个 单一的连续控制流程,是一个执行路径,是调度执行的单位

           (1)线程又称为轻量级进程

           (2)Threads run at the same time, independently of one another,即运行的同时,是彼此独立的

           (3)一个进程可以有多个并行(concurrent)的线程

           (4)一个进程中的线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,他们从同一堆中分配对象、通信、数据交换、同步操作等

           (5)由于线程间的通信是在同一地址空间 进行,所以不需要额外的通信机制,这就使得通信更简便,信息传递也更快

1.2、进程与线程的关系

 一个进程中至少有一个线程

如图所示,

(1)Java虚拟机启动的时候,会有一个进程Java.exe,该进程中至少有一个线程,在负责Java程序的执行,而这个线程运行的代码存在于main方法中,该线程称之为主线程

(2)一个进程中的线程共享代码和数据空间

(3)线程结束,进程未必结束,但进程结束,线程一定结束

(4)进程中包含线程,线程是进程的一部分

如下,将他们的区别展示如下:

 1.3、常见问题

1、单核CPU设定多线程的意义

       CPU的执行时按照时间片分的,而不是时间点。一个CPU,一个时间段内只能运行一个线程,而一个进程进行,有多个操作(线程),线程1正在执行,消耗CPU,而线程2、3、4没有执行,在等待执行。CPU切换线程并不会管你线程是否将代码执行完,而是和分给线程的时间片是否到期有关,时间片到期了就会切换线程,并发也就由此产生了。我们进一步说,线程主要分为两类,一类是CPU密集型,大量时间在做计算,另一类是IO密集型,大量时间等待输入/输出。

2、工作线程数(线程池中线程数量)设置多少合适?

     一般压测,去找到合适的数量。有一个计算公式:N_threads = N_cpu * U_cpu * (1+\frac{W}{C})

            Ucpu :CPU的利用率

            W:线程等待时间

            C:线程计算时间

(1)根据CPU的核数,计算能力设置,但也不是核数有多少,就设置多少个线程数,因为一个程序运行,除了要运行的线程,还有GC等其他线程也需要运行;

(2)另外,出于安全的考虑,把CPU利用80%即可,如有紧急情况,还有20%的空间可以利用。

那么针对公式,线程的等到时间和计算时间这么确定呢?一般使用工具Profiler,关于工具的使用,我们后续介绍。

2、多线程的实现

      多线程的实现,有3种方法,分别是继承Thread类、实现Runnable接口、实现Callable与Future,具体如下:

1、继承Thread类

         (1)定义Thread类的子类MyThread,并重写run方法,该run方法的方法体就代表了线程要完成的任务,把run方法称为执行体

         (2)创建MyThread类的实例,即创建了线程对象

         (3)调用线程对象的start()方法来启动该线程

public class ThreadTest01 extends Thread {
    //重写run方法
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("第" + i + "次thread运行");
        }
    }

    public static void main(String[] args) {
        //创建对象,就创建好一个线程
        ThreadTest01 d = new ThreadTest01();
        //d.run()方法启动线程要使用start方法,start方法会去调用run方法
        d.start();
        /**
         * 主线程
         */
        for (int i = 0; i < 5; i++) {
            System.out.println("main-->" + i);
        }
    }
}

 运行结果如下:

 这里说一下线程的执行过程,如图所示,清晰明了。这里我语言阐述一下,Thread来的run方法是存储线程要运行的代码,而主线程要运行的代码存放在main方法中,main方法启动后,加载了main主线程,main主线程又new出了一个对象,并调用start方法,开启多线程执行,打印输出第多少次thread运行,线程间切换后,执行主线程的代码,打印输出main--->i的结果。现在有这2个线程在同时执行,谁先抢占到CPU资源,谁就先执行,所以每次运行的效果都不相同。

2、实现Runnable接口

     (1)定义Runnable接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的线程执行体;

     (2)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;

    (3)调用线程对象的start()方法来启动该线程

public class RunableDemo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("第" + i + "次thread运行");
        }
    }

    public static void main(String[] args) {
        //创建对象,就创建好一个线程
        RunableDemo runableDemo = new RunableDemo();
        Thread thread = new Thread(runableDemo);
        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main-->" + i);
        }
    }
}

执行结果如图所示

总结

         前面2种方法,继承Thread类的方式的缺点是如果我们的类已经从一个类继承,那么无法再继承Thread类了,而通过Runnable接口实现多线程,避免单继承,又方便共享资源,同一份资源,多个代理访问。

3、使用线程池

    使用线程池来实现,后续文章介绍。

 3、线程的状态

看图说话,线程的状态如图所示

 新生状态New

      用new关键字建立一个线程后,该线程对象就处于新生状态。

      处于新生状态的线程又自己的内存空间,通过调用start方法进入就绪状态。

就绪状态ready

     处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU。

     当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称为CPU调度。

运行状态Running

     在运行状态的线程执行自己的run方法中代码,直到等待某资源阻塞或完成任务而死亡。

     如果在给定的时间盘片内没有执行结束,就会被系统给换下来回到等待执行状态ready。

就绪状态ready和运行状态Running都是Runnable状态

阻塞状态

     处于运行状态的线程在某些情况下,如执行了sleep方法,或等待I/O设备等资源,将让出CPU并暂时停止自己运行,进入阻塞状态。

     在阻塞状态下的线程不能进入就绪队列,只有当引起阻塞的原因解除后,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。

死亡状态

    死亡状态是线程生命周期中的最后一个阶段。死亡状态的原因有3个,一个是正常运行的线程完成了它的全部工作,另外一个是线程被强制性地终止,如通过stop方法来终止(但不推荐使用),三是线程抛出未捕获的异常。

1、线程操作的相关方法

 2、暂停线程执行

    有3种方法可以暂停线程执行

   1、sleep

           不会释放锁,sleep时,别的线程也不可以访问锁定对象

   2、yield

          让出CPU的使用权,从运行状态直接进入就绪状态,让CPU重新挑选一个线程进入运行状态

   3、join

         当某个线程等待另一个线程执行结束后,才继续执行时,使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值