美文网首页
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线程

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