001-多线程-基础-进程线程、线程状态、优先级、用户线程和守护线程

一、进程与线程

1、DOS系统【单进程系统】

  最早的时候DOS有一个特点:只要电脑有病毒,那么电脑就死机了。
  原因:传统的DOS系统属于单进程系统,即:在同一时间段内只允许有一个程序运行。

2、Windows系统【多进程多线程】

  电脑中毒也可以运行,但是会变慢
  原因:因为在一个cpu、一块资源的情况下,程序利用一些轮转算法,可以让一个资源在一个时间段可以同时处理多个程序(进程),但是在一个时间点上只允许一个进程去执行。
  windows:任务管理器
  linux:ps
  在每一个进程上可以划分出若干个线程,那么线程的操作一定是要比进程更快。多线程操作要超多进程操作。但是所有的线程都一定是要在进程的基础上进行划分。
  所以进程一旦消失,那么线程一定消失
  线程依附于进程存在。

  进程:一个进程就是一个“执行中的程序”,是程序在计算机上的一次运行活动。程序要运行,系统就在内存中为该程序分配一块独立的内存空间,载入程序代码和资源进行执行。程序运行期间该内存空间不能被其他进程直接访问。系统以进程为基本单位进行系统资源的调度和分配。

  线程:程序的执行具体是通过线程来完成的,所以一个进程中至少有一个线程。回忆一下 HelloWrold 程序中main方法的执行,其实这时候,Java虚拟机会开启一个名为“main”的线程来执行程序代码。一个进程可以包含多个线程,这些线程共享数据空间和资源,但又分别拥有各自的执行堆栈和程序计数器。线程是CPU调度的基本单位。

3、进程和线程的关系

  这个问题很经典,无数面试官想从这个问题得到你对线程和进程的理解程度,想完全讲清楚有点儿难度,同时也需要篇幅。从程序开发角度来讲,进程是资源分配的基本单位,是一个程序或者服务的基本单位。我们可以说进程就是程序的执行过程,这个过程包括很多东西,如CPU执行时间、运行内存、数据等,而且是一个动态的过程。线程是轻量级的进程,它们是共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动,每个CPU核心在同一时刻只能执行一个线程,尽管我们有时感觉自己的计算机同时开着多个任务,其实他们每个的执行都是走走停停的,CPU轮流给每个进程及线程分配时间。总结一下二者关系:
  a>.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
  b>.资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
  c>.处理机分给线程,即真正在处理机上运行的是线程。
  d>.线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
更为详细的解释,操作系统原理相关方面的书籍

4、为什么需要并发编程

  目前计算机基本是基于X86架构的,而基于X86架构的机器主频超不过4GHz,随着信息时代的来临,我们需要处理的数据越来越大,因此对程序的性能也要求越来越高,提高程序的性能,一方面需要提高运行环境的配置,也就是配性能更好的机器,更快的CPU更大的内存,另一方面就是优化自己的程序,从前者的角度考虑,我们说当前计算机的CPU主频已经接近顶级,一段时间内不可能再高,也就是说想通过提高机器的性能来加快程序的运行是一个不好的选择,因为你需要投入更多的硬件。同时其实我们的程序在运行的时候,CPU很多时候都是空闲的状态,因为程序不光有CPU调度,而大多数耗时的操作都在于IO上,我们知道程序的运行速度取决于木桶原理,CPU再快IO跟不上也没有用。所以我们需要合理利用空闲的CPU,当IO在处理其它的时候,CPU可以继续工作,这就是多线程编程。

  多线程编程的好处,总结一句话就是:合理利用CPU的空闲时间,来提高程序的性能。

5、利用多线程开发解决哪些问题

  在Java里有很多地方都在用多线程实现,尽管有时你没有发现,如Swing和SWT开发,当我们点击一个按钮时,我们总不能等待后台的数据处理完在给我们跳转吧,这样用户体验实在是太差了,我们可以先完成跳转,再加一些可以让用户等待的业务(如缓冲条等),然后再呈现结果。还有如我们开发Web时必用的Servlet,天生具有多线程性质,因为对于网站来说,同时又多个访问是最起码的需求,我们绝对不可能一个接一个的处理请求,那样根本不符合现实。这些地方就用到了多线程,并发地处理多个请求,提高程序的响应速度。

6、多线程编程的利弊

利:多线程总会让程序跑的更快

弊:如何完美的解决并发带来的线程安全(解决现程安全就会用到同步,同步的话又会带来性能的下降)

二、java的线程介绍【线程状态、优先级、用户线程和守护线程】

每个线程都有自己的局本部变量表、程序计数器、以及生命周期

2.1、java线程的生命周期【线程状态】

  java线程在他的生命周期内有几种不同的状态:线程初始化,启动,运行和死亡。

  

上图所示的状态解释如下:【可通过thread dump查看】【核心状态6个:new、runnable(running、ready)、waiting、timed_waiting、terminaled、blocked】

  ● new 是指线程被初始化,但是还没有调用其start方法,还没有开始执行

    每一个线程,在堆内存中都有一个对应的Thread对象。Thread t = new Thread();当刚刚在堆内存中创建Thread对象,还没有调用t.start()方法之前,线程就处在NEW状态。在这个状态上,线程与普通的java对象没有什么区别,就仅仅是一个堆内存中的对象。

  ● runnable 调用线程的start方法之后,线程开始执行其任务,这时候线程是运行状态

    该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。 这个状态的线程比较正常,但如果线程长时间停留在在这个状态就不正常了,这说明线程运行的时间很长(存在性能问题),或者是线程一直得不得执行的机会(存在线程饥饿的问题)。

    Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

  ● waiting 有时候线程需要等待另外一个线程执行完毕之后再执行,这时候线程处于等待状态,处于等待状态的线程需要其他线程notify之后才能恢复到运行状态

    处在该线程的状态,正在等待某个事件的发生,只有特定的条件满足,才能获得执行机会。而产生这个特定的事件,通常都是另一个线程。也就是说,如果不发生特定的事件,那么处在该状态的线程一直等待,不能获取执行的机会。比如说,A线程调用了obj对象的obj.wait()方法,如果没有线程调用obj.notify或obj.notifyAll,那么A线程就没有办法恢复运行;如果A线程调用了LockSupport.park(),没有别的线程调用LockSupport.unpark(A),那么A没有办法恢复运行。

  ● timed waiting 运行中的线程可以进入到定时等待的状态,这时候线程间隔指定的时间间隔之后就会恢复到运行状态。超时等待,该状态不同于WAITING,它可以在指定的时间后自行返回。

    J.U.C中很多与线程相关类,都提供了限时版本和不限时版本的API。TIMED_WAITING意味着线程调用了限时版本的API,正在等待时间流逝;当等待时间过去后,线程一样可以恢复运行。如果线程进入了WAITING状态,一定要特定的事件发生才能恢复运行;而处在TIMED_WAITING的线程,如果特定的事件发生或者是时间流逝完毕,都会恢复运行。

  ● terminated 当线程任务执行完毕或者被abort的时候线程处于终止状态

    线程执行完毕,执行完run方法正常返回,或者抛出了运行时异常而结束,线程都会停留在这个状态。这个时候线程只剩下Thread对象了,没有什么用了。

  • blocked 表示线程阻塞于锁。

  核心状态,在Thread.State这个枚举类型中定义:

    public enum State {
        // Thread state for a thread which has not yet started.
        NEW,

        // runnable、ready
        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;
    }

2.2、java线程的优先级  

  每一个java线程都有一个优先级,操作系统可以通过线程的优先级决定决定将cpu分配给哪个线程。优先级越高的线程越可能得到cpu资源。
  java线程优先级的值在1-10之间,1是常量MIN_PRIORITY,10是常量MAX_PRIORITY 。默认情况下java的线程的优先级是NORM_PRIORITY 即5.
  高优先级的线程通常更重要,更有可能获得cpu时间资源,但是并不能保证绝对可以获得cpu。

2.3、用户线程和守护线程

  java中线程分为两种类型:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。如果不设置此属性,默认为用户线程
  用户线程和守护线程的区别:【用户线程存活程序存活,其他则结束
    1. 主线程结束后用户线程还会继续运行,JVM存活;主线程结束后守护线程和JVM的状态又下面第2条确定。
    2.如果没有用户线程,都是守护线程,那么JVM结束(随之而来的是所有的一切烟消云散,包括所有的守护线程)。

  main方法说明

  1.Main线程是个非守护线程,不能设置成守护线程。
    main线程是由java虚拟机在启动的时候创建的,进入程序的入口。main方法开始执行的时候,main用户线程已经创建好并在运行了。对于运行中的线程,不可以调用Thread.setDaemon(),调用会抛出异常Exception in thread "main"
  2.Main线程结束,如果还有其他用户线程则继续运行,如果都是守护线程则直接结束
    主线程,只是个普通的非守护线程,用来启动应用程序,不能设置成守护线程;除此之外,它跟其他非守护线程没有什么不同。主线程执行结束,其他用户线程一样可以正常执行。
    按照操作系统的理论,进程是资源分配的基本单位,线程是CPU调度的基本单位。对于CPU来说,其实并不存在java的主线程和子线程之分,都只是个普通的线程。进程的资源是线程共享的,只要进程还在,线程就可以正常执行,换句话说线程是强依赖于进程的。也就是说,线程其实并不存在互相依赖的关系,一个线程的死亡从理论上来说,不会对其他线程有什么影响。
    java虚拟机(相当于进程)退出的时机是:虚拟机中所有存活的线程都是守护线程。只要还有存活的非守护线程虚拟机就不会退出,而是等待非守护线程执行完毕;反之,如果虚拟机中的线程都是守护线程,那么不管这些线程的死活java虚拟机都会退出。

  补充说明:

    定义:守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。
    优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
    设置:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在线程启动用线程对象的setDaemon方法。
    示例: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
    生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出

    例子程序:

      thread = new Thread(this);
      thread.setDaemon(true);
      thread.start();
     当java虚拟机中没有非守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底 会出现什么问题。

示例:

    public static void main(String[] args) throws Exception {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                String daemon = Thread.currentThread().isDaemon() ? "daemon" : "not daemon";
                while (true) {
                    System.out.println("Im is running " + daemon + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        };
        Thread thread = new Thread(r);
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(3000);
        System.out.println("main is over");
    }
View Code

  运行上面程序,输出如下内容后程序就退出了。
    Im is running daemon14:14:48
    Im is running daemon14:14:49
    Im is running daemon14:14:50
    main is over

  可以看到在主线程退出之后,deamon线程也就被终止了,同时程序也就退出了。
  我们对上面程序稍作改动,将t.setDaemon(true)注释掉,再看下运行结果。
    Im is running not daemon14:15:29
    Im is running not daemon14:15:30
    Im is running not daemon14:15:31
    main is over
    Im is running not daemon14:15:32
    Im is running not daemon14:15:33

    ……

  可以看到在主线程退出之后,t线程还在继续执行,这是因为线程t默认情况下是非守护线程,尽管主线程退出了,他还是在继续执行着。
  需要注意设置线程是否为守护线程必须在其执行之前进行设置,否则会抛出异常IllegalThreadStateException。这一点可以从Thread类的setDaemon(boolean)的源码中得到求证。如下源码:

    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

  可以看到如果线程在live状态调用setDaemon会抛出异常。

三、多线程

  在一定范围内,多线程数量的增加会明显提升整个系统的吞吐性能,但是线程本身会极大的耗费内存空间,线程的频繁创建和回收也极其占用CPU资源。

  线程的利用必须掌握在一个度,太少的线程可能浪费CPU资源,而太多极有可能反而降低整个应用性能。

  推荐使用线程池处理

 

posted @ 2017-09-24 18:58  bjlhx15  阅读(553)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭