Thread线程从零认识到深层理解——初识

线程系列目录

  1. Thread线程从零认识到深层理解——初识
  2. Thread线程从零认识到深层理解——六大状态
  3. Thread线程从零认识到深层理解——wait()与notify()
  4. Thread线程从零认识到深层理解——线程安全
  5. 线程池从零认识到深层理解——初识
  6. 线程池从零认识到深层理解——进阶

博客创建时间:2020.09.28
博客更新时间:2021.02.25

注意:本系列博文源码分析取自于Android SDK=30,与网络上的一些源码可能不一样,可能他们分析的源码更旧,无需大惊小怪。


前言

在讲解线程知识的时候必不可少的需要线科普下什么是线程,线程与进程之间的关系。

1. 进程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。进程是表示资源分配的基本单位,又是调度运行的基本单位。

2. 线程
线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。
线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行。

进程与线程的关系

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。
  3. 处理CPU片分给线程,即真正在处理机上运行的是线程。
  4. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

线程的调度问题,这里有一个很好的举例来说明:
如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答。此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)


一、为什么要用多线程?

线程是最小的执行单位,当程序中有多个线程是,最明显的利好就是能提高程序的执行效率不用彼此等待。除此还有其他诸多优点:

  1. 更高的运行效率,——并行;
  2. 多线程是模块化的编程模型;
  3. 与进程相比,线程的创建和切换开销更小,通信更方便;
  4. 简化程序的结构,便于理解和维护;更高的资源利用率。
  5. 每个线程之间独立互不影响,即使一个线程阻塞也不会影响其他线程
    public class Thread implements Runnable {
    
    
        ...
        private Runnable target ;
        /**
        * 线程优先级
        */
        private int priority;

        /**
         *如果线程中target不为null,则执行target.run()。
         * 否则run()无实际意义,需要要求Thread的子类必须重写run()方法
         */
        @Override
        public void run() {
    
    
            if (target != null) {
    
    
                target.run();
            }
        }
        ...
    }

由代码可知,Thread本身实现了Runnable,但是它自身有一个Runnable成员Runnable ,在run方法中默认调用的是target.run()。


二、线程的常见属性

priority优先级

优先级的使用意义
当在某个线程中运行创建一个新的 Thread对象时, 新的线程的优先级默认等于创建线程的优先级,且是否是守护进程线程也同创建线程一致,如需设置优先需在创建时给定设置,后面不能再修改。

如果UI线程创建出了十几个工作线程,为了不让工作线程和主线程抢占CPU资源,需要工作线程的优先级进行降级,让CPU能够识别主次,提高主线程能够得到的系统资源。

优先级设定
每个线程都有优先级属性,具有较高优先级的线程优先于优先级较低的线程执行,每个线程可能会被标记为守护进程。

线程的优先级用数字来表示,范围从1~10,主线程的默认优先级为5,在线程构造初始化后,可以通过set方法设置具体值。
Thread.MIN_PRIORITY=1;Thread.MAX_PRIORITY=10;Thread.NORM_PRIORITY=5 )

三、线程的常见方法

构造函数
Thread的构造函数有很多种,我这里只列举几种常见的构造方法。

	public Thread() {
    
    
        init(null, null, "Thread-" + nextThreadNum(), 0);
     }
    public Thread(Runnable target) {
    
    
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(ThreadGroup group, Runnable target) {
    
    
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(String name) {
    
    
        init(null, null, name, 0);
    }
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
    
    
        init(group, target, name, stackSize);
    }

通过源码分析,最终都走向了init()这个方法,我们来进行源码分析。

	/**
     * Initializes a Thread.
     * 初始化一个线程
     *
     * @param g         the Thread group  线程组
     * @param target    the object whose run() method gets called  调用run()方法的对象
     * @param name      the name of the new Thread    新线程的名称
     * @param stackSize the desired stack size for the new thread, or
     *                  zero to indicate that this parameter is to be ignored.
     *                  新线程的所需堆栈大小,或0表示该参数将被忽略
     * @param acc       the AccessControlContext to inherit, or
     *                  AccessController.getContext() if null
     *                  要继承的AccessControlContext;如果为null,则为* AccessController.getContext()
     */
   private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    
    
        if (name == null) {
    
    
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        // 当前正在执行的线程,即创建该线程正在运行的线程
        Thread parent = currentThread();
        //如果未设置ThreadGroup,则默认与创建者线程在同一group中
        if (g == null) {
    
    
            g = parent.getThreadGroup();
        }
        //增加线程组中未启动线程的数量,如果ThreadGroup未destroyed则nUnstartedThreads++,即使该线程最终未启动也会nUnstartedThreads++
        g.addUnstarted();
        this.group = g;
        //如果创建者程序时守护程序线程,则它创建的线程也是守护程序线程,除非手动修改setDaemon(boolean on)
        this.daemon = parent.isDaemon();
        //默认新线程与父线程的优先级是一样的,如果想修改优先级需要额外调用setPriority(int newPriority)方法进行重新设定
        this.priority = parent.getPriority();
        this.target = target;

        init2(parent);
        //设置线程请求的堆栈大小
        this.stackSize = stackSize;
        //为下个Thread Id做准备 ++ threadSeqNumber
        tid = nextThreadID();
    }

根据分析init()方法可以获悉如下:

  1. 这是一个线程的初始化方法,创建Thread最终都会执行该方法
  2. 线程必须有线程名,如果创建者未定义线程名,则系统默认会取名"Thread-" + nextThreadNum(),如"Thread-643636"。后面的数字由线程的一个全局静态变量threadInitNumber决定,它会自增。
  3. 增加ThreadGroup 中未启动线程的数量,如果ThreadGroup未destroyed则nUnstartedThreads++,即使该线程最终未启动也会nUnstartedThreads++
  4. 在初始化方法中daemon、priority 等属性默认与父线程相同,不过Thread中提供了相关set方法,可以在线程创建完毕后自定义修改

run()方法
线程实现了Runnable接口,需要重写Runnable#run()方法。

在我们继承Thread重写run()方法时,常常看到super.run会不会感到奇怪,这个有啥用。其实这里调用的是Runnable对象target#run()。target是private的且只能通过Thread的构造函数进行赋值,所以如果在Thread创建的构造函数中未传target参数,其实super.run()无卵用。

private class TestThread : Thread() {
    
    
    override fun run() {
    
    
        super.run()
        println("TestThread =$name")
    }
}

我通过测试发现如果Thread的子类既重写了自身的run()方法,有实现了Runnable重写了Runnable的run()方法,则两个方法中的代码都会执行。

    @JvmStatic
    fun main(args: Array<String>) {
    
    
        object : Thread(Runnable {
    
      println("执行Runnable中的run方法") }) {
    
    
            override fun run() {
    
    
                super.run()
                println("执行Thread中的run方法")
            }
        }.start()
    }
	运行结果:
	执行Runnable中的run方法
	执行Thread中的run方法

start()方法

    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>使该线程开始执行; Java虚拟机调用此线程的`run`方法。
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
    
    
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM.
         * VM创建设置的main方法线程或系统group线程不会调用此方法
         * Any new functionality added to this method in the future may have to also be added to the VM.
         * 新的特性或许未来会在此增加
         * A zero status value corresponds to state "NEW".
         */
        if (started)
            throw new IllegalThreadStateException();
        /*
         * 通知组该线程即将开始,以便可以将其添加到组的线程列表中,并且该组的未启动计数可以减少nUnstartedThreads--
         */
        group.add(this);
        started = false;
        try {
    
    
            // 使用Android特定的nativeCreate()方法创建/启动线程
            nativeCreate(this, stackSize, daemon);
            started = true;
        } finally {
    
    
            try {
    
    
                if (!started) {
    
    
                    // start失败
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
    
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

通过start()方法的注释和代码分析,知道:

  1. 当线程的实例调用start()方法后,JVM会调用该线程的run()方法
  2. 一旦线程已经启动,started=true,如果再次调用start()方法将抛IllegalThreadStateException
  3. VM创建设置的main方法线程或系统group线程不会调用此方法
  4. 线程一旦执行完成,则无法再次调用start()启动。
  5. Thread的启动实际是由native方法 nativeCreate启动的,它是Android特有的显示提供的方法,其虚拟机中是调用的是start0()。
  6. 由于内存不够,线程数超过限制等原因,nativeCreate()调用可能失败,此时需要将该Thread从线程组列表中移出,nUnstartedThreads++

stop()方法

   @Deprecated
    public final void stop() {
    
    
        /*
         * The VM can handle all thread states stop0(new ThreadDeath());
        */
        throw new UnsupportedOperationException();
    }

stop方法很简单,此方法最初旨在强制线程停止*并抛出{@code ThreadDeath}作为异常,本质上是不安全的。所以它已被@Deprecated标记,实际开发中请不要在使用该方法,如确实需要停止线程请使用中断的方式。线程中断在后面将会详细描述。

在旧的版本中stop()方法是这样的:

    @Deprecated
	public final void stop() {
    
    
   		stop(new ThreadDeath());
	}

旧版SDK中通过stop(new ThreadDeath())来停止线程,它实质调用了native方法中的stop0()方法,它的方法实质也是抛出了一个UnsupportedOperationException()异常。在调用stop()方法时抛出UnsupportedOperationException异常,意在强制停止线程是不安全和被认可的。


sleep

        /**
         * Causes the currently executing thread to sleep (temporarily cease
         * execution) for the specified number of milliseconds,
         * subject to the precision and accuracy of system timers and schedulers.
         * 使当前正在执行的线程在指定的毫秒数内进入睡眠状态(暂时停止执行),这取决于系统计时器和调度程序的精度和准确性
         * The thread does not lose ownership of any monitors.
         * 该线程不会失去任何锁的所有权。
         *
         * @param millis 睡眠时间(以毫秒为单位)
         * @throws IllegalArgumentException 如果{@code millis}的值为负
         * @throws InterruptedException     if any thread has interrupted the current thread. The
         *                                  <i>interrupted status</i> of the current thread is
         *                                  cleared when this exception is thrown.
         *                                  如果有任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
         */
        public static void sleep(long millis) throws InterruptedException {
    
    
            sleep(millis, 0);
        }

        /**
         * 在安卓中使用共享的native来实现sleep()方法
         */
        @FastNative
        private static native void sleep(Object lock, long millis, int nanos)
                throws InterruptedException;
        
        /**
         * Causes the currently executing thread to sleep (temporarily cease
         * execution) for the specified number of milliseconds plus the specified
         * number of nanoseconds, subject to the precision and accuracy of system
         * timers and schedulers.
         * 根据系统计时器和调度程序的精度和准确性,使当前正在执行的线程进入睡眠状态(暂时停止*执行)
         * 指定的毫秒数加上指定的*纳秒数。
         *
         * @param millis 睡眠时间(以毫秒为单位)
         * @param nanos  {@code 0-999999}额外的纳秒睡眠时间
         * @throws IllegalArgumentException 如果{@code millis}的值为负,或者* {@code nanos}的值不
         *                                  在{@code 0-999999}范围内
         * @throws InterruptedException     如果有任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
         */
        public static void sleep(long millis, int nanos)
                throws InterruptedException {
    
    
            if (millis < 0) {
    
    
                throw new IllegalArgumentException("millis < 0: " + millis);
            }
            if (nanos < 0) {
    
    
                throw new IllegalArgumentException("nanos < 0: " + nanos);
            }
            if (nanos > 999999) {
    
    
                throw new IllegalArgumentException("nanos > 999999: " + nanos);
            }

            if (millis == 0 && nanos == 0) {
    
    
                if (Thread.interrupted()) {
    
    
                    throw new InterruptedException();
                }
                return;
            }

            final int nanosPerMilli = 1000000;
            long start = System.nanoTime();
            long duration = (millis * nanosPerMilli) + nanos;

            Object lock = currentThread().lock;

            // The native sleep(...) method actually performs a special type of wait,
            // 本机sleep(...)方法实际上执行一种特殊的等待
            // which may return early, so loop until sleep duration passes.
            // 它可能会提早返回,所以循环直到睡眠持续时间过去
            synchronized (lock) {
    
    
                while (true) {
    
    
                    sleep(lock, millis, nanos);

                    long now = System.nanoTime();
                    long elapsed = now - start;

                    if (elapsed >= duration) {
    
    
                        break;
                    }

                    duration -= elapsed;
                    start = now;
                    millis = duration / nanosPerMilli;
                    nanos = (int) (duration % nanosPerMilli);
                }
            }
        }
    }
  1. 使当前正在执行的线程在指定的毫秒数内进入睡眠状态(暂时停止执行),这取决于系统计时器和调度程序的精度和准确性。即sleep(1000),事实上算上代码执行时间可能它运行了1000.2ms。
  2. 执行sleep()方法后,线程处于TIMED_WAITING状态,但是该线程不会失去任何锁的所有权。当超过指定时间线程自动苏醒进入就绪状态
  3. Object.wait()方法调用后会释放对象锁,而线程的Thread.sleep()方法不会让出对象锁,只会让出CPU时间片。两者都会让线程进入WAITING状态

yield

   /**
         * 向调度程序提示当前线程愿意产生当前使用的处理器.调度程序可以随意忽略此提示
         *
         * Yield是一种启发式尝试,旨在提高线程之间的相对进程否则会过度利用CPU
         *
         * 应将其使用与详细的性能分析和基准测试结合使用,以确保它确实具有所需的效果。
         * 很少适合使用此方法。 *对于调试或测试目的可能是有用的,在某些情况下它可能有助于重现*由于竞争条件而引起的错误。
         * 在设计并发控制结构(例如* {@link java.util.concurrent.locks}包中的结构)时,它可能也很有用。
         */
        public static native void yield();

yield()方法是一种native方法,一般很少有机会使用该方法。它是一种让步方法,调用该方法可使当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。

实际使用中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。


join

        /**
         * Waits at most {@code millis} milliseconds for this thread to
         * die. A timeout of {@code 0} means to wait forever.
         * 等待最多{@code millis}毫秒以使该线程死亡。 {@code 0}超时意味着永远等待。
         * 如ThreadB中执行ThreadA#join(50),则等待50ms后ThreadB死亡
         *
         * <p> This implementation uses a loop of {@code this.wait} calls
         * conditioned on {@code this.isAlive}.
         * As a thread terminates the {@code this.notifyAll} method is invoked.
         * It is recommended that applications not use {@code wait}, {@code notify}, or
         * {@code notifyAll} on {@code Thread} instances.
         * 建议应用程序不要在{@code Thread}实例上使用{@code wait},{@code notify}或{@code notifyAll}。
         *
         */
        // 在单独的锁对象而不是此线程上同步
        public final void join(long millis) throws InterruptedException {
    
    
            synchronized (lock) {
    
    
                long base = System.currentTimeMillis();
                long now = 0;

                if (millis < 0) {
    
    
                    throw new IllegalArgumentException("timeout value is negative");
                }

                if (millis == 0) {
    
    
                    while (isAlive()) {
    
    
                        lock.wait(0);
                    }
                } else {
    
    
                    while (isAlive()) {
    
    
                        long delay = millis - now;
                        if (delay <= 0) {
    
    
                            break;
                        }
                        lock.wait(delay);
                        now = System.currentTimeMillis() - base;
                    }
                }
            }
        }
        
        /**
         * @param millis the time to wait in milliseconds
         * @param nanos  {@code 0-999999} additional nanoseconds to wait
         * @throws IllegalArgumentException 如果{@code millis}的值为负,或者{@code nanos}的值
         *                                  不在{@code 0-999999}范围内
         * @throws InterruptedException     如果有任何线程中断了当前线程。抛出此异常时,将清除当前线程的中断状态
         */
        // 在单独的锁对象而非此线程上同步。
        public final void join(long millis, int nanos) throws InterruptedException {
    
    

            synchronized (lock) {
    
    
                if (millis < 0) {
    
    
                    throw new IllegalArgumentException("timeout value is negative");
                }
                if (nanos < 0 || nanos > 999999) {
    
    
                    throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
                }
                if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
    
    
                    millis++;
                }
                join(millis);
            }
        }

        public final void join() throws InterruptedException {
    
    
            join(0);
        }

  1. join()或者join(long)一般使用在一个线程中调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态继续执行代码。
  2. join()方法会响应线程中断抛出InterruptedException异常

setPriority
设置线程的优先级,线程优先级在1~10,不在范围则抛异常。

    /**
     * 修改线程的优先级,默认是Thread Group的最大优先级,一般为5
     *
     * @exception  IllegalArgumentException  不在1~10范围内,则抛异常
     * @exception  SecurityException  if the current thread cannot modify this thread. 
     */
    public final void setPriority(int newPriority) {
    
    
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
    
    
            // Android-changed: Improve exception message when the new priority is out of bounds.
            throw new IllegalArgumentException("Priority out of range: " + newPriority);
        }
        if((g = getThreadGroup()) != null) {
    
    
            if (newPriority > g.getMaxPriority()) {
    
    
                newPriority = g.getMaxPriority();
            }
            // Android-changed: Avoid native call if Thread is not yet started.
            // setPriority0(priority = newPriority);
            synchronized(this) {
    
    
                this.priority = newPriority;
                if (isAlive()) {
    
    
                    // BEGIN Android-added: Customize behavior of Thread.setPriority().
                    // http://b/139521784
                    // setPriority0(newPriority);
                    ThreadPrioritySetter threadPrioritySetter =
                        RuntimeHooks.getThreadPrioritySetter();
                    int nativeTid = this.getNativeTid();
                    if (threadPrioritySetter != null && nativeTid != 0) {
    
    
                        threadPrioritySetter.setPriority(nativeTid, newPriority);
                    } else {
    
    
                        setPriority0(newPriority);
                    }
                    // END Android-added: Customize behavior of Thread.setPriority().
                }
            }
        }
    }

四、如何使用线程

线程使用方法有三种如下:

  1. 继承Tread类创建线程
    private class TestThread extends Thread{
    
    
        @Override
        public void run() {
    
    
            super.run();
            ...
        }
    }

	new TestThread().start();
  1. 实现Runnable接口创建线程
  private class TestRunnable implements Runnable{
    
    
        @Override
        public void run() {
    
    
            ...
        }
  }
  
  new Thread(new TestRunnable()).start();  
  1. 继承Tread类创建线程
       FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    
    
            @Override
            public Integer call() throws Exception {
    
    
                ...
                return null;
            }
        });

        new Thread(futureTask).start();

三种方式比较:

  1. Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活。它只是用来启动线程而已,执行代码体一般不放这里。
  2. Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
  3. Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行。
  4. Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

总结

本篇博文主要讲解Thread的创建、使用及常用属性方法,适合新手学习阅读。对于对线程学习有更高要求的同学请继续阅读Thread系列中的其他文章。


相关链接

  1. Thread线程从零认识到深层理解——初识
  2. Thread线程从零认识到深层理解——六大状态
  3. Thread线程从零认识到深层理解——wait()与notify()
  4. Thread线程从零认识到深层理解——线程安全
  5. 线程池从零认识到深层理解——初识
  6. 线程池从零认识到深层理解——进阶

扩展链接:

  1. Android CameraX 使用入门
  2. Android Studio 4.0新特性及升级异常

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

猜你喜欢

转载自blog.csdn.net/luo_boke/article/details/108826958