• java创建线程的两种方式及源码解析


    创建线程的方式有很多种,下面我们就最基本的两种方式进行说明。主要先介绍使用方式,再从源码角度进行解析。

    • 继承Thread类的方式
    • 实现Runnable接口的方式

    这两种方式是最基本的创建线程的方式,其实核心也就是Thread类,后面分析源码会讲到,下面先介绍使用方式。

    一:继承Thread类的方式创建线程


    1,创建线程步骤

      • 创建一个子类继承于Thread类
      • 子类重写Thread类的run方法,方法内实现子线程要完成的功能
      • 创建一个子类的对象
      • 调用子类对象的start()的方法,该方法有两个作用:启动此线程;调用重写的run方法。

    代码如下:

    package com.yefengyu.thread;
    
    //1,创建一个子类继承于Thread类 
    public class SubThread extends Thread {
    
        //2,子类重写Thread类的run方法,方法内实现子线程要完成的功能 
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread ...." + i);
            }
        }
    }
    package com.yefengyu.thread;
    
    
    public class TestThread {
    
        public static void main(String[] args) {
            //3,创建一个子类的对象。
            SubThread subThread = new SubThread();
            //4,调用线程的start()的方法。该方法有两个作用:启动此线程;调用响应的run方法。不能显示调用run方法,因为这样不能开启线程
            subThread.start();
    
            for (int i = 0; i < 5; i++) {
                System.out.println("main ...." + i);
            }
        }
    }

    每次执行结果都不相同,这是因为多个线程都在获取cpu的执行权,cpu执行到谁,就执行谁。但是要明确一点,在某个时刻,单个cpu只能执行一个线程,cpu进行着快速切换,已达到看上去是并行执行的效果,这就是线程的一个特点:随机性,哪个线程抢到cpu资源,就执行该线程,至于执行多长时间,是cpu说了算。

    main ....0
    Thread ....0
    Thread ....1
    Thread ....2
    main ....1
    main ....2
    main ....3
    main ....4
    Thread ....3
    Thread ....4

    2,设置与获取线程名称

    由于默认的线程名称没有可读性,因此设置一个线程名称还是比较重要的,Thread类有个构造方法:

        public Thread(String name) {
            init(null, null, name, 0);
        }

    因此子类增加线程名称则比较简单:

    package com.yefengyu.thread;
    
    //1,创建一个子类继承于Thread类
    public class SubThread extends Thread {
    
        //通过构造函数设置线程名称,当然使用set方法也可以
        public SubThread(String name) {
            super(name);
        }
    
        //2,子类重写Thread类的run方法,方法内实现子线程要完成的功能
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                //下面两种方法都会获取线程名称
                System.out.println(this.getName()+" ... " + i);
                System.out.println(Thread.currentThread().getName()+" *** " + i);
            }
        }
    }
    package com.yefengyu.thread;
    
    
    public class TestThread {
    
        public static void main(String[] args) {
            //(3)创建一个子类的对象。
            SubThread subThread1 = new SubThread("my-thread11111");
            SubThread subThread2 = new SubThread("my-thread22222");
            //(4)调用线程的start()的方法。该方法有两个作用:启动此线程;调用响应的run方法。不能显示调用run方法,因为这样不能开启线程
            subThread1.start();
            subThread2.start();
    
            for (int i = 0; i < 5; i++) {
                System.out.println("main ...." + i);
            }
        }
    }

    结果:

    main ....0
    my-thread22222 ... 0
    my-thread11111 ... 0
    my-thread22222 *** 0
    my-thread22222 ... 1
    my-thread22222 *** 1
    main ....1
    my-thread22222 ... 2
    my-thread11111 *** 0
    my-thread22222 *** 2
    main ....2
    main ....3
    main ....4
    my-thread22222 ... 3
    my-thread11111 ... 1
    my-thread11111 *** 1
    my-thread11111 ... 2
    my-thread22222 *** 3
    my-thread22222 ... 4
    my-thread22222 *** 4
    my-thread11111 *** 2
    my-thread11111 ... 3
    my-thread11111 *** 3
    my-thread11111 ... 4
    my-thread11111 *** 4

    3,实战:汽车票买票程序

    假如车站有3张票,三个窗口,多线程如何卖票?

    package com.yefengyu.thread;
    
    public class SubThread extends Thread {
    
        //票的总数
        private int ticket = 3;
    
        public SubThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖票 " + ticket--);
                }
            }
        }
    }
    package com.yefengyu.thread;
    
    
    public class TestThread {
    
        public static void main(String[] args) {
            //三个线程模拟三个窗口同时卖票
            SubThread subThread1 = new SubThread("my-thread11111");
            SubThread subThread2 = new SubThread("my-thread22222");
            SubThread subThread3 = new SubThread("my-thread33333");
            subThread1.start();
            subThread2.start();
            subThread3.start();
        }
    }

    结果和我们想的大不相同,竟然每个线程都卖了3张票,一共卖了9张票,这是不可以忍受的。

    my-thread11111卖票 3
    my-thread11111卖票 2
    my-thread11111卖票 1
    my-thread33333卖票 3
    my-thread22222卖票 3
    my-thread33333卖票 2
    my-thread22222卖票 2
    my-thread33333卖票 1
    my-thread22222卖票 1

    修改1:使用静态变量:

    //票的总数
    private static int ticket = 3;

    定义静态可以解决卖出多余票的情况,但是这种变量一般不定义静态的,因为静态属性生命周期太长。

    修改2:new一个SubThread实例,多次启动

    package com.yefengyu.thread;
    
    public class TestThread {
        public static void main(String[] args) {
           //一个线程对象多次启动
           SubThread subThread1 = new SubThread("my-thread11111");
           subThread1.start();
           subThread1.start();
           subThread1.start();
        }
    }

    出现异常:

    my-thread11111卖票 3Exception in thread "main" 
    my-thread11111卖票 2
    my-thread11111卖票 1
    java.lang.IllegalThreadStateException
        at java.lang.Thread.start(Thread.java:708)
        at com.yefengyu.thread.TestThread.main(TestThread.java:8)

    源码这样说:线程不是NEW状态是不可以调用start方法的,调用会报异常,也就是一个线程启动之后不能再启动。关于线程状态后面博文会讲到。

    /**
    * A zero status value corresponds to state "NEW".
    */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    如何解决卖票程序中的问题呢?使用Runnable接口。

    二:实现Runnable接口的方式创建多线程


    1,创建线程步骤

      • 编写一个类实现Runnable接口
      • 该类实现run方法
      • 创建该类的对象
      • 在创建 Thread 时作为一个参数来传递并启动

    2,代码演示

    package com.yefengyu.thread;
    
    public class SubThread implements Runnable {
    
        //票的总数
        private int ticket = 3;
    
        @Override
        public void run() {
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖票 " + ticket--);
                }
            }
        }
    }
    package com.yefengyu.thread;
    
    public class TestThread {
        public static void main(String[] args) {
            
            //创建该类的对象
           SubThread subThread = new SubThread();
    
            //在创建 Thread 时作为一个参数来传递并启动
           new Thread(subThread, "线程1").start();
           new Thread(subThread, "线程2").start();
           new Thread(subThread, "线程3").start();
        }
    }

    运行结果如下,是我们想要的结果。

    线程1卖票 3
    线程3卖票 2
    线程2卖票 1

          稍微研究一下,第一种继承Thread类的方式,new了3次SubThread实例,那么实例的变量ticket也有三个,线程分别拥有各自的ticket。而实现Runnable接口的方式,数据只存在与SubThread这个实例对象中,代码中只需new一次,因此只有一份ticket数据,而将持有这份数据的对象通过构造方法传入到多个线程中的时候,线程对象只是执行的载体,真实数据只有一份,因此Runnable接口的实现方式适合多个相同的程序代码的线程去处理同一个资源。

    3,本节小总结

    • 采用继承Thread类方式 
      • 优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
      • 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类
    • 采用实现Runnable接口方式: 
      • 优点:线程类只是实现了Runable接口,还可以继承其他的类。可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况。
      • 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

    三:源码分析


    1,理论分析

    多线程是java开发中必不可少的一项技术点,下面主要研究Thread类,通过分析该类了解多线程执行的过程,为以后的线程池等高级技术打下坚实基础。

    当我们使用多线程进行开发的时候,最开始学习的例子就是使用Thread类。使用步骤如下,和上面演示的对比,简化了步骤:

      • 编写一个类,继承Thread

      • 重写run方法

      • 通过调用start方法启动线程。

    后来又有一种方法,实现Runnable接口,主要步骤如下:

      • 编写一个类,实现Runnable 接口,重写run方法

      • 将该类的对象传入Thread类中

      • 通过调用start方法启动线程。

    通过上面,我们来分析一下,线程执行离不开Thread类,最后都要使用start方法启动线程。我们可以想到start方法是一个入口方法,它可以做很多事。假如start方法做了如下的事情:

      • do x

      • do y

      • do run

      • do z

    我们不关心x、y、z具体是什么,只要明白start方法是一个入口方法,它做了很多事情,但是在某一步,它调用了 run 方法。而run方法是我们必须实现的,也就是我们自己实现的逻辑在start里面被执行了。接着我们考虑下 run 方法,怎么样才能自己定义run方法,然后在run方法里面写自己的逻辑?

    • 一种方法是,在Thread类里面,我们定义一个抽象方法 run,这个时候,必须有子类来实现。这是模板设计模式思想。

    • 另一种方法是提供一个接口,并且接口中有个方法 run,Thread类持有这个接口(通过属性持有,再通过构造器传入),并且Thread类也有个run方法(为啥也要有个run方法后面会提到),该run方法调用接口的run方法。此时只要编写一个类实现接口,重写run方法,并传入Thread类,那么Thread类在执行start方法的时候,会调用自身的run方法,该run方法又会调用接口实现的run方法,这是策略设计模式思想。

    以上两种模式就是实现Thread类和实现Runnable接口的实现原理。需要注意的是,Thread类本身也实现了Runnable接口,那么Thread类本身拥有run方法则水到渠成。

    2,源码分析

    Runnable的源码很简单:

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }

    Thread类的属性非常多,我们暂时不管,注意有一个属性,它是对Runnable接口的引用。

    private Runnable target;

    有了属性,我们需要看如何传入这个属性值:

    public Thread() {
         init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(Runnable target) {
         init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    init方法内容很多,但是关于target变量,只有一句:

    this.target = target;

    因此可以判断,上面两个构造器,第一个没有给Runnable赋值,值为null,第二个通过参数进行赋值。

     

    我们再看下Thread类:

    public class Thread implements Runnable

    Thread类实现了Runnable接口,因此必须实现run方法:

        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }

    对于这个run方法,如果是使用继承Thread类重写run方法;那么这里面的内容将会被覆盖,如果是实现Runnable接口重写run方法,那么此处就会调用接口实现的run方法。这就让两种实现多线程的方式得以共存。

    这个run方法何时调用?在start方法中:

    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();

    注意start方法中的start0方法和最后一行start0方法。该start0方法是native方法,实质是调用run方法,此处暂时不做详解。

    Thread类使用模板设计模式,模板方法是start,start方法里面的start0方法才能真正启动线程、调用了Thread类的run方法。

    3,Runnable接口的好处:

    • 适合多个相同的程序代码的线程去处理同一个资源
    • 可以避免java中的单继承的限制
    • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
    • 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

  • 相关阅读:
    LYDSY模拟赛day3 序列
    LYDSY模拟赛day3 涂色游戏
    LYDSY模拟赛day3 平均数
    hdu1757 A Simple Math Problem
    清北国庆day1 (脑)残
    poj3070 Fibonacci
    uva10870 递推关系Recurrences
    湖南附中模拟day1 瞭望塔
    湖南附中模拟day1 收银员
    湖南附中模拟day1 金坷垃
  • 原文地址:https://www.cnblogs.com/ye-feng-yu/p/11840562.html
Copyright © 2020-2023  润新知