美文网首页
Thread线程知识点讲解

Thread线程知识点讲解

作者: 神易风 | 来源:发表于2022-03-29 11:03 被阅读0次

    本文出处Thread线程知识点讲解 转载请说明出处

    内部属性

    //线程名,如果创建时没有指定则使用Thread- + 创建序列号
    private volatile String name;
       //线程优先级  Java只是给操作系统一个优先级的参考值,线程最终在操作系统的优先级是多少还是由操作系统决定。
        private int priority;
    
        //守护线程 
        private boolean daemon = false;
    
        //为JVM保留字段
        private boolean stillborn = false;
        private long eetop;
    
        /* What will be run. */
        private Runnable target;
    
        //线程组,每一个线程必定存于一个线程组中,线程不能独立于线程组外
        private ThreadGroup group;
    
        // 类加载器,当线程需要加载类时,会使用内部类加器
        private ClassLoader contextClassLoader;
    
        /* For autonumbering anonymous threads. */
        private static int threadInitNumber;
        private static synchronized int nextThreadNum() {
            return threadInitNumber++;
        }
    
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
        /*
         * The requested stack size for this thread, or 0 if the creator did
         * not specify a stack size.  It is up to the VM to do whatever it
         * likes with this number; some VMs will ignore it.
         */
        private final long stackSize;
    
        /*
         * JVM-private state that persists after native thread termination.
         */
        private long nativeParkEventPointer;
    
        /*
         * Thread ID
         */
        private final long tid;
    
        /* For generating thread ID */
        private static long threadSeqNumber;
    
        // 这个线程号是整个Thread 类共享的
        private static synchronized long nextThreadID() {
            return ++threadSeqNumber;
        }
    
        /*
         * 线程状态
         */
        private volatile int threadStatus;
    

    构造函数

        public Thread() {
            this(null, null, "Thread-" + nextThreadNum(), 0);
        }
    
        public Thread(ThreadGroup group, Runnable target, String name,
                      long stackSize) {
            this(group, target, name, stackSize, null, true);
        }
    
        private Thread(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(); //从创建Thread 的线程中获取到父线程
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                /* Determine if it's an applet or not */
    
                /* If there is a security manager, ask the security manager
                   what to do. */
                if (security != null) {
                    g = security.getThreadGroup();
                }
    
                /* If the security manager doesn't have a strong opinion
                   on the matter, use the parent thread group. */
                if (g == null) { //没有设置线程组则使用当前线程的线程组
                    g = parent.getThreadGroup();
                }
            }
    
            /* checkAccess regardless of whether or not threadgroup is
               explicitly passed in. */
            g.checkAccess();
    
            /*
             * Do we have the required permissions?
             */
            if (security != null) {
                if (isCCLOverridden(getClass())) {
                    security.checkPermission(
                            SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
                }
            }
            //对没有启动线程进行计数
            g.addUnstarted();
    
            this.group = g;
            //如果在创建线程时没有设置守护线程,优先级、类加器这些,全部都是当前现场的
            this.daemon = parent.isDaemon();
            this.priority = parent.getPriority();
            if (security == null || isCCLOverridden(parent.getClass()))
                this.contextClassLoader = parent.getContextClassLoader();
            else
                this.contextClassLoader = parent.contextClassLoader;
            this.inheritedAccessControlContext =
                    acc != null ? acc : AccessController.getContext();
            this.target = target;
            setPriority(priority);
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;
    
            /* Set thread ID */
            this.tid = nextThreadID();
        }
    

    构造方法其实都是对Thread 内部属性进行初始化,比如线程名、优先级、类加器、线程Id。如果没有设置这些属性全部继承自当前的。让我比较奇怪是非常重要的threadStatus 没有赋值,而是使用了默认值,我猜想这个变量全程都是由c++来变更的,所以不必要使用Java进行赋值。
    已经初始化的线程对象可以通过set方法去修改守护线程、线程名、优先级。

    线程状态

     public enum State {
            /**
             * Thread state for a thread which has not yet started.
             */
            NEW,
    
            /**
             * Thread state for a runnable thread.  A thread in the runnable
             * state is executing in the Java virtual machine but it may
             * be waiting for other resources from the operating system
             * such as processor.
             */
            RUNNABLE,
    
            /**
             * Thread state for a thread blocked waiting for a monitor lock.
             * A thread in the blocked state is waiting for a monitor lock
             * to enter a synchronized block/method or
             * reenter a synchronized block/method after calling
             * {@link Object#wait() Object.wait}.
             */
            BLOCKED,
    
            /**
             * Thread state for a waiting thread.
             * A thread is in the waiting state due to calling one of the
             * following methods:
             * <ul>
             *   <li>{@link Object#wait() Object.wait} with no timeout</li>
             *   <li>{@link #join() Thread.join} with no timeout</li>
             *   <li>{@link LockSupport#park() LockSupport.park}</li>
             * </ul>
             *
             * <p>A thread in the waiting state is waiting for another thread to
             * perform a particular action.
             *
             * For example, a thread that has called {@code Object.wait()}
             * on an object is waiting for another thread to call
             * {@code Object.notify()} or {@code Object.notifyAll()} on
             * that object. A thread that has called {@code Thread.join()}
             * is waiting for a specified thread to terminate.
             */
            WAITING,
    
            /**
             * Thread state for a waiting thread with a specified waiting time.
             * A thread is in the timed waiting state due to calling one of
             * the following methods with a specified positive waiting time:
             * <ul>
             *   <li>{@link #sleep Thread.sleep}</li>
             *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
             *   <li>{@link #join(long) Thread.join} with timeout</li>
             *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
             *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
             * </ul>
             */
            TIMED_WAITING,
    
            /**
             * Thread state for a terminated thread.
             * The thread has completed execution.
             */
            TERMINATED;
        }
    

    线程状态经常被问于面试中,几个状态和代表涵义大家都有记一记。

    状态 描述 场景
    NEW Thread线程刚刚被创建,创建状态 new Thread
    RUNNABLE 运行状态,线程正在运行中 Thread.start
    BLOCKED 堵塞状态 synchronized 竞争失败
    WAITING 等待,这种状态要么无限等待下去,要么被唤醒 Object.wait、Lock
    TIMED_WAITING 等待超时,在等待时设置了时间,到时会自动唤醒 Thread.sleep、LockSupport.parkNanos
    TERMINATED 死亡状态 线程已经执行完任务

    从下图可以发现从创建-> 运行-> 死亡 这个过程是不可逆的。


    image.png

    线程运行和停止

        public synchronized void start() {
            /**
             * This method is not invoked for the main method thread or "system"
             * group threads created/set up by the VM. 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 (threadStatus != 0)  //状态必须是创建状态  NEW  ,防止一个对象多次调用start 方法
                throw new IllegalThreadStateException();
    
            /* Notify the group that this thread is about to be started
             * so that it can be added to the group's list of threads
             * and the group's unstarted count can be decremented. */
            group.add(this);  //加入线程组容器中,未开始线程数-1  
    
            boolean started = false;
            try {
                start0();  
                started = true;
            } finally {
                try {
                     // 进入到这里,则start0 创建一个线程失败了,要从线程组中删除它,未开始线程再加回来
                    if (!started) {  
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    
        private native void start0();
    

    start方法比较简单的,先判断状态是否正确,在创建之前加入到线程组里面,失败了再移除。start0 方法应该就是调用系统资源真正去创建一个线程了,而且线程状态也是由这个方法修改的。

    run方法只有使用Thread来创建线程,并且使用Runnable传参才会执行这里run方法,继承方式应该是直接调用子类run方法了。

        public void run() {
            if (target != null) {  //有传入Runnable 对象,则调用该对象实现run方法
                target.run();
            }
        }
    

    stop方法虽然在Java2已经被官方停用了,很值得去了解下的。

        @Deprecated(since="1.2")
        public final void stop() {
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                checkAccess();
                if (this != Thread.currentThread()) {
                    security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
                }
            }
            // A zero status value corresponds to "NEW", it can't change to
            // not-NEW because we hold the lock.
            if (threadStatus != 0) { //不是NEW,线程已经运行了,如果被挂起了,需要对它进行唤醒
                resume(); // Wake up thread if it was suspended; no-op otherwise
            }
    
            // The VM can handle all thread states
            stop0(new ThreadDeath()); //停止线程,并且抛出一个异常给JVM
        }
    
        private native void stop0(Object o);
    

    看完这个方法,也没有看出来stop()能干什么,我也不是很清楚这个stop能干什么,我将写几个例子验证功能。
    创建几个线程去执行下任务,执行一会后,对所有线程调用stop方法,是否会退出任务。

    public class ThreadStopTest {
    
        public static void main(String[] args) {
            ThreadStopTest t = new ThreadStopTest();
            Runnable r = () -> {
              int i = 0;
              while (i < 1000){
                  t.spinMills(500);
                  System.out.println(Thread.currentThread().getName() + " : " + i);
                  i++;
              }
            };
    
            Thread t1 = new Thread(r);
            Thread t2 = new Thread(r);
            Thread t3 = new Thread(r);
            t1.start();
            t2.start();
            t3.start();
            t.spinMills(2000);
            t1.stop();
            t2.stop();
            t3.stop();
        }
    
        public void spinMills(long millisecond){
           long start = System.currentTimeMillis();
           while (System.currentTimeMillis() - start < millisecond){
               //自旋 ,模拟执行任务
           }
        }
    }
    

    执行结果

    Thread-1 : 0
    Thread-0 : 0
    Thread-2 : 0
    Thread-1 : 1
    Thread-0 : 1
    Thread-2 : 1
    Thread-2 : 2
    Thread-1 : 2
    Thread-0 : 2
    

    调用完stop方法,线程立刻退出任务,连一个异常都没有抛出的,真的是非常干脆。如果有人不下心使用stop方法,出现问题都非常难排除,所以Java 官方早早就停止使用它了,详细看官方说明

    如果想优雅停止一个正在运行的线程,官方建议使用interrupted()。线程中断就是目标线程发送一个中断信号,能够收到中断信号线程自己实现退出逻辑。简单点说就是线程A在干活,突然有个人对它做了一个动作,线程A在知道这个动作涵义,它会知道自己要停下来。说白这就一个动作,如果线程逻辑没有处理这个动作代码,线程并不会退出的。看下Thread类里面有那些方法。

    方法 备注
    interrupt() 中断目标线程,给目标线程发一个中断信号,线程被打上中断标记
    isInterrupted() 判断目标线程是否被中断,不会清除中断标记
    interrupted 判断目标线程是否被中断,会清除中断标记

    实现一个简单例子

        public static void main(String[] args) throws InterruptedException {
            Runnable r = () -> {
                while (!Thread.currentThread().isInterrupted()){
                    //do some
                    System.out.println(System.currentTimeMillis());
                }
                System.out.println("线程准备退出啦");
                Thread.interrupted();
            };
            Thread t = new Thread(r);
            t.start();
            Thread.sleep(1000);
            t.interrupt();
        }
    

    上面代码核心是中断状态,如果中断被清除了,那程序不会跳出while循环的,下面改一下,添加一个sleep方法

        public static void main(String[] args) throws InterruptedException {
            Runnable r = () -> {
                while (!Thread.currentThread().isInterrupted()){
                    //do some
                    try {
                        Thread.sleep(400);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(System.currentTimeMillis());
                }
                System.out.println("线程准备退出啦");
                Thread.interrupted();
            };
            Thread t = new Thread(r);
            t.start();
            Thread.sleep(1000);
            t.interrupt();
        }
    

    执行结果 : 发送中断后,Thread.sleep直接抛出一个异常,并不会跳出循环。
    因为sleep会响应中断,抛出一个中断异常,再清除线程中断状态。再回到while 判断时,中断状态已经被清除了,继续循环下去。
    sleep()是一个静态native 方法,使当前执行的线程休眠指定时间,但是休眠的线程不会放弃监控器的锁(synchronized),当任何线程要中断当前线程时,会抛出InterruptedException异常,并且清理当前线程的中断状态。所以在方法调用上就会抛出这个异常,让调用者去处理中断异常。

    join和yield方法

    join()就是一个等待方法,等待当前线程任务执行后,再次唤醒被调用的线程,常常用来控制多线程任务执行顺序。

        /**
         * Waits at most {@code millis} milliseconds for this thread to
         * die. A timeout of {@code 0} means to wait forever.
         *
         * <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.
         *
         * @param  millis
         *         the time to wait in milliseconds
         *
         * @throws  IllegalArgumentException
         *          if the value of {@code millis} is negative
         *
         * @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 final synchronized void join(final long millis)
        throws InterruptedException {
            if (millis > 0) {
                if (isAlive()) {  //这里获取线程状态,只是不是开始和死亡就算alive了
                    final long startTime = System.nanoTime();
                    long delay = millis;
                    do {
                        wait(delay);
                    } while (isAlive() && (delay = millis -
                            TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0); //在指定时间内沉睡
                }
            } else if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
                throw new IllegalArgumentException("timeout value is negative");
            }
        }
    

    想了解方法主要看方法注释就行,在指定时间内等待被调用者的线程死亡,如果没有死亡时间到了会自行唤醒,如果时间为0则永远等待下去,直到执行线程执行完任务。唤醒是由notifyAll执行的,但是没看见在哪里执行这个方法。查了一下资料知道每个线程执行完成后都会调用exit()方法,在exit会调用notifyAll。
    yield(): 单词翻译过来就是让步的意思。主要作用当线程获取到执行权时,调用这个方法会主动让出执行器,它跟上面wait、sleep 不同,线程状态是没有改变的,此时任然是RUN。比如一个线程获取锁失败了,这时线程什么不能干,获取锁本身是很快,此时将线程挂起了,有点得不偿失,不如此时让出CPU执行器,让其他线程去执行。既不会浪费CPU宝贵时间,也不需要太耗费性能。这个方法经常用于java.util.concurrent.locks包下同步方法,看过并发工具类的同学应该都认识它。

    线程间协作

    wait方法让当前线程进入等待状态(WAITING),并且释放监控锁,只有当其他线程调用notify或者notifyAll才会唤醒线程。
    notify唤醒一个在等待状态的线程,重新进入RUNNABLE状态。
    notifyAll唤醒所有正在等待状态的线程,重新进入RUNNABLE状态。
    上面三个方法都必须在监控锁(synchronized)下使用,不然会抛出IllegalMonitorStateException。
    wait、notify 两个方法结合就可以实现线程之间协作。比如最经典的生产者-消费者模型: 当上游消费者发送发送信息太多,导致队列挤压已经满了,这时消费者这边可以使用wait,让生产者停下里,当消费者已经开始消费了,此时队列已经被消费走一个信息了,有空间了,消费者可以调用notify,让上游生产者继续运作起来。当队列里面信息已经被消费完时,消费者会调用wait,让线程进入等待中,当上游线程有信息发送到队列时,此时队列中信息就不是全空的了,就可以调用wait 唤醒一个等待消费者。这样就可以形成线程之间相互通信的效果了。
    简单实现消费者-生产者模型

        public void push(T t){
            synchronized (lock){
                size++;
                if (size == QUEUE_CAPACTIY) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
                //入队列中
            }
        }
    
        public T poll(){
            synchronized (lock){
                size--;
                if (size == 0) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
                return T;
            }
        }
    

    Callable和 Thread关系

    我们知道了所有的线程其实都是Thread.start去创建的,重写run 方法达到异常执行任务,但是Callable这个接口是否也是使用Thread或者Runnable接口,主要看FutureTask就知道如何实现了。
    看下run方法

        public void run() {
               //如果线程已经被创建了,则不需要再次执行任务了
            if (state != NEW ||
                !RUNNER.compareAndSet(this, null, Thread.currentThread()))  
                return;
            try {
                Callable<V> c = callable;  //callable 方法实现类
                if (c != null && state == NEW) { //刚刚初始化的状态
                    V result;
                    boolean ran;
                    try {
                        result = c.call(); //执行任务
                        ran = true;
                    } catch (Throwable ex) {
                        result = null;
                        ran = false;
                        setException(ex); //保存异常,将等待队列的线程全部唤醒过来
                    }
                    if (ran)
                        set(result); //保存执行结果,将等待队列的线程全部唤醒过来
                }
            } finally {
                // runner must be non-null until state is settled to
                // prevent concurrent calls to run()
                runner = null;
                // state must be re-read after nulling runner to prevent
                // leaked interrupts
                int s = state;
                if (s >= INTERRUPTING)
                    handlePossibleCancellationInterrupt(s);
            }
        }
    

    可以看出Callable仍然是使用Thread来创建线程的,内部通过维护state来判断任务状态,在run 方法中执行call方法,保存异常和执行结果。
    看下get() 如何获取执行结果的吧

        public V get() throws InterruptedException, ExecutionException {
            int s = state;
            if (s <= COMPLETING)  //还在执行中
                s = awaitDone(false, 0L);  //等待任务执行完成或者中断,会堵塞调用线程
            return report(s);
        }
    
       /**
         * Awaits completion or aborts on interrupt or timeout.
         *
         * @param timed true if use timed waits
         * @param nanos time to wait, if timed
         * @return state upon completion or at timeout
         */
        private int awaitDone(boolean timed, long nanos)
            throws InterruptedException {
            // The code below is very delicate, to achieve these goals:
            // - call nanoTime exactly once for each call to park
            // - if nanos <= 0L, return promptly without allocation or nanoTime
            // - if nanos == Long.MIN_VALUE, don't underflow
            // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
            //   and we suffer a spurious wakeup, we will do no worse than
            //   to park-spin for a while
            long startTime = 0L;    // Special value 0L means not yet parked
            WaitNode q = null;
            boolean queued = false;
            for (;;) { 
                int s = state;
                if (s > COMPLETING) { //如果状态已经有执行中变成其他 ,直接将状态返回
                    if (q != null)
                        q.thread = null;
                    return s;
                }
                else if (s == COMPLETING) //正在执行中,让出CPU执行权,而不是变换线程状态
                    // We may have already promised (via isDone) that we are done
                    // so never return empty-handed or throw InterruptedException
                    Thread.yield();
                else if (Thread.interrupted()) { //处理线程中断,退出自旋
                    removeWaiter(q);  //删除队列中的线程
                    throw new InterruptedException();
                }
                else if (q == null) {
                    if (timed && nanos <= 0L)
                        return s;
                    q = new WaitNode();
                }
                else if (!queued)  //将等待结果线程放入一个队列中,其实这个队列就是来处理等待结果线程的中断的
                    queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
                else if (timed) {
                    final long parkNanos;
                    if (startTime == 0L) { // first time
                        startTime = System.nanoTime();
                        if (startTime == 0L)
                            startTime = 1L;
                        parkNanos = nanos;
                    } else {
                        long elapsed = System.nanoTime() - startTime;
                        if (elapsed >= nanos) {
                            removeWaiter(q);
                            return state;
                        }
                        parkNanos = nanos - elapsed;
                    }
                    // nanoTime may be slow; recheck before parking
                    if (state < COMPLETING) //任务没有启动,挂起等待线程
                        LockSupport.parkNanos(this, parkNanos);
                }
                else
                    LockSupport.park(this); //任务没有开始,挂起调用者,任务完成后会将它唤醒的
            }
        }
    

    现在基本就明了,使用run 调用call方法,将执行结果保存起来,然后get 方法这边使用自旋方法等待执行结果,并且使用队列将等待的线程保存起来,来处理线程的唤醒、中断。

    总结

    这里简单说了Thread的构造方法,属性设置,比较重要就是线程几个状态,状态流转、线程启动停止,中断处理,几个常用方法的介绍。简单说了下FutureTask实现原理,结合上面提到的知识点,上面提到这些知识都是挺重要的,你可以看到大部分Java并发类都用到这些知识来开发的,频繁出现在面试中也是可以理解的。

    相关文章

      网友评论

          本文标题:Thread线程知识点讲解

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