美文网首页
5、认识java线程

5、认识java线程

作者: 小manong | 来源:发表于2019-04-15 22:33 被阅读0次
  • 对于计算机来说一个任务就是一个进程,进程是资源分配的最小单位,一个进程内存可能跑着多个线程,有时也成线程为轻量级进程,线程共享进程的资源,线程是资源调度的最小单位。
  • 线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器及其各自的生命周期,当启动一个java程序时候,操作系统就会创建一个java进程(jvm进程),jvm进程中将会派生或者创建很多的线程。

一、线程生命周期

  • 线程的生命周期大概可以分为5个主要阶段:new、runnable、running、blocked、terminated


    线程生命周期状态图
1、线程new状态
  • 当new了一个thread对象但是没有执行start方法启动线程,此时这个线程的状态就是new状态,此时的thread对象和普通对象没有区别。

new状态可以转化为runnable状态

2、runnable状态
  • runnable也称为可执行状态,当new状态下执行start方法后就进入runnable状态,此时jvm真正的创建了一个线程。此时线程能否立即执行还需要等待cpu的调度。

严格来说,runnable的线程只能意外终止或者进入running状态

3、running状态
  • 一旦获取到cpu的调度,此时的线程就是running状态,才可以真正的执行自己的代码逻辑,在running状态可以发生的转换:

1、直接进入terminated状态:使用stop(jdk不推荐)或者使用标志位或者意外死亡
2、进入blocked状态:使用sleep、wait等jdk方法;进行网络操作时候的读写阻塞等;为了获取到锁从而加入到了阻塞队列中
3、进入runnable状态:cpu的时间片用完,主动调yield方法放弃时间片。

4、blocked状态
  • 阻塞状态又细分为sleep造成的阻塞、网络阻塞、wait等待阻塞、为了获取锁等待阻塞等

1、直接进入terminated状态:使用stop(jdk不推荐)或者使用标志位或者意外死亡
2、进入runnable状态:比如网络操作阻塞结束、sleep休眠结束、被notify唤醒、获取到了某一个锁资源、线程阻塞期间被打断(比如interrupt方法)

5、terminated状态
  • 是一种最终状态,该状态下的线程不会切换到任何其他状态下。导致这个状态发生的原因有:线程正常结束、线程运行时出现错误、jvm crash导致的所有线程死亡

二、创建及执行线程

1、start方法源码解析
//执行这个方法时候,jvm会调用该线程的run方法,而start0方法是本地方法,
//换言之就是说run方法是被本地方法start0调用的。
public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
              
            }
        }
    }
//是一个本地方法
private native void start0();

1、thread被new之后,实际上此时的状态为0(threadState=0)
2、不能两次启动Thread,否则抛throw new IllegalThreadStateException();
3、线程启动后会被加入到ThreadGroup中
4、一个线程生命周期结束之后,也就是terminated状态,再次调用start方法是不被允许的,会抛异常。

2、模板方法在Thread中的使用(继承方式实现Thread)
  • Thread实现Runnable接口,在Thread中run方法源码如下。可以看出如果我们不传入一个Runnable实现,那么Thread中的run方法实际上就是一个空方法,也就是说我们创建线程时候(如果不实现Runnable接口)就要自己实现run方法,这就是一个典型的模板方法(父类编写算法结构,子类自己实现
private Runnable target;
 @Override
    public void run() {
      //如果实现了Runnable接口,就会执行实现Runnable接口的方法
        if (target != null) {
            target.run();
        }
    //否则就重写run中的方法
    }

  • 案例说明(先不考虑线程安全问题):
public class TicketWindow extends Thread {
    /**
     * 柜台业务
     */
    private final String name;
    /**
     * 最大受理50单
     */
    private static final int MAX = 50;
    /**
     * 从编号为1的开始(注意是静态的,是类所有的,唯一的)
     */
    private static int index = 1;

    public TicketWindow(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        //模板方法,子类实现
        while (index <= MAX) {
            System.out.println("柜台:" + name + ",当前的编号为:" + index++);
        }
    }
    public static void main(String[] args) {
        TicketWindow t1=new TicketWindow("t1");
        t1.start();
        TicketWindow t2=new TicketWindow("t2");
        t2.start();
        TicketWindow t3=new TicketWindow("t3");
        t3.start();
    }
}
3、策略模式在Thread中使用(Runnable接口实现)
  • 创建线程只有一种方式,就是实现Thread类,而实现线程的执行单元(run方法)有两个方式。一种是前面说过的重写run方法,还有一种就是实现Runnable接口,然后将Runnable实例构造Thread的参数。
  • 策略模式:其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。
public class TicketWindowRunnable implements Runnable {
    /**
     * 注意可以不需要static修饰
     */
    private int index = 1;

    private static final int MAX = 10;

    @Override
    public void run() {
        //模板方法,子类实现
        while (index <= MAX) {
            System.out.println("柜台:" + Thread.currentThread().getName() 
                                     + ",当前的编号为:" + index++);
        }
    }

    public static void main(String[] args) {
        TicketWindowRunnable task = new TicketWindowRunnable();
        Thread t1=new Thread(task,"t1");
        Thread t2=new Thread(task,"t2");
        Thread t3=new Thread(task,"t3");
        t1.start();
        t2.start();
        t3.start();
    }
}
4、Thread构造和Runnable构造线程的区别

(1)从面相对象角度看,Thread构造是通过继承的方式、而Runnable构造是通过组合的方式,继承的方式相对于组合的方式耦合更加紧密,因此优先推荐使用组合的方式
(2)从共享对象的角度看,Runnable构造方式意味着多个线程实例可以共享一个Runnable实例,而Thread构造不能共享run方法,如果需要实现共享,需要把相关的属性设置为static修饰的类变量。

5、线程与普通对象的区别
  • 线程就是一个对象,但是创建一个线程与创建其他对象类型不同,jvm会为每一个线程分配调用栈需要的内存空间,调用栈用于跟踪java代码间的调用关系以及java代码对本地代码的调用。另外,每一个线程可能还有一个内核线程与之对应,因此创建一个线程比创建其他对象类型的成本要高一些。

三、线程构造

1、线程名称

(1)默认命名

 public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
/* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

(2)线程命名

  • 可以在Thread构造函数中传入命名,比如
- public Thread(String name) 
- public Thread(ThreadGroup group, String name)
- public Thread(Runnable target, String name)

(3)修改线程命名

  • 在线程调用start方法之前还能进行线程名字的修改,一旦启动了线程,那么就不能修改线程名称了。
public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }
2、线程的父子关系
  • 创建任何一个线程都会有一个父线程,通过下面的源码可以的出结论(一个线程的创建肯定由另一个县城完成;被创建线程的父线程是创建它的线程)
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        //获取当前线程作为父线程
        Thread parent = currentThread();
...
3、Thread与虚拟机栈
  • 每一个线程在创建的时候,jvm都会为其创建对象的虚拟机栈和jvm堆内存,虚拟机栈通过-xss配置,堆内存使用-XMS和-Xmx配置。与线程创建、运行、销毁等关系比较大的是虚拟机栈,而且虚拟机栈内存的划分大小将直接决定一个Jvm中可以创建多少个线程。
  • 线程的创建数量是随着虚拟机内存的增多而减少的,是一种反比关系。近似公式:java进程内存=堆内存+线程数量*栈内存;而堆内存作为进程内存的基数,它的增大对线程数量的影响也是成反比的,但是没有虚拟机栈影响更深刻。


    image.png
4、守护线程
public class DeamonThread {
    public static void main(String[] args) throws InterruptedException {
        //1、开始main线程
        Thread thread=new Thread(()->{
            while (true){
                System.out.println("hahahha");
            }
        });
        //2、设置thread线程为守护线程
        thread.setDaemon(true);
        //3、开始thread线程
        thread.start();

        Thread.sleep(2000);
        //4、main线程结束
        System.out.println("main finished");
    }
}
  • 上面是一个关于守护线程的案例,如果注释掉2,jvm永远不会退出,即时是main线程结束了生命周期,原因是jvm进程中还存在一个非守护线程在执行
  • 守护线程:是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
  • 将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

  • 守护线程常用作后台线程,当需要关闭某些线程时候或者退出jvm时候,一些线程能够自动退出,此时就可以考虑使用守护线程了

三、所线程的编程的优势及其风险



相关文章

  • 5、认识java线程

    对于计算机来说一个任务就是一个进程,进程是资源分配的最小单位,一个进程内存可能跑着多个线程,有时也成线程为轻量级进...

  • java线程入门基础(二)

    java线程入门基础(二) 一、认识Java里的线程 1.1 Java里的程序天生就是多线程的 一个Java程序从...

  • JAVA基础之并发

    1、概述 Java5中对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还...

  • Java多线程-线程基础

    1.线程概念 2.线程状态 3.Java线程提供的API 4.Java Object提供的API 5.线程协作方式...

  • using

    JAVA md5 将json转化为java对象 转化xmlToJSONjson转xml 遍历map: 线程池线程池...

  • Java知识梳理六

    一、Java多线程一 1.谈谈线程的生命周期和状态转移 关于线程生命周期的不同状态,在Java 5以后,线程...

  • 深入Java线程(二)

    为了理解可先看深入Java线程(一)内容在看本篇。 线程生命周期 关于线程生命周期的不同状态,在 Java 5 以...

  • Java多线程之线程池深入讲解

    1 线程池介绍 1.1 线程池概念 Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Jav...

  • Java多线程模型

    Java多线程模型 生命周期 Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡 5 个状态。一个 Java...

  • ExecutorService

    ExecutorService扩展和实现Executor。 java 线程池的5种状态 RUNNING 线程池...

网友评论

      本文标题:5、认识java线程

      本文链接:https://www.haomeiwen.com/subject/mkohwqtx.html