JDK源码阅读(三):Thread类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aimomo007/article/details/89328701

线程和进程的区别

进程就是计算机中正在执行的程序,每个进程都有自己独立的一块内存空间和一组资源,比如Windows操作系统可同时运行多个程序,这里每个运行的程序都是一个进程,这些程序使用的内存空间和系统资源都是独立的,互不干扰。

线程可以被看成是进程的一部分,也就是把进程完成的任务分成一个个更小的子任务,每一个子任务就是一个线程,他们共享进程的内存空间和资源。

Thread

Thread类是Java种对于线程的实现方式,线程分为如下几种状态:

线程的状态切换

根据线程的状态分析其源码的实现原理

创建线程

Thread类的构造函数如下:

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, String name) {
        init(group, null, name, 0);
    }

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

public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
    }

public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

从源代码中可以看出,所有的构造函数最终都调用了init方法

/**
     * Initializes a Thread.
     *
     * @param g 线程组
     * @param target 线程真正要执行的任务
     * @param name 线程的名称
     * @param stackSize  
     * @param acc 
     * @param inheritThreadLocals 
     */
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();
        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 doesn't have a strong opinion of 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(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 */
        tid = nextThreadID();
    }

这段源代码中出现了几个新得知识点:ThreadGroup(线程组)、ThreadLocal(线程本地变量)、Runnable。Runnable实际上就是真正的任务,同时还有Callable,将在后面进行介绍。

ThreadGroup

线程组线程池一样,都是用来管理一堆线程,但是线程组主要是方便对一组线程的属性进行管理,例如设置线程组中的所有线程为守护线程,线程组中线程的最大优先级,统一处理线程组中线程抛出的异常等,而线程池主要是管理线程的生命周期。

创建线程组的时候,可以指定其父线程,默认在创建该线程组的线程所在的线程组,创建线程的时候,可以指定线程所在的线程组,默认是创建该线程所在的线程组

例如在main方法中创建一个线程appThread和一个线程组spiderGroup都没有指定线程组,则它们的关系如下:

线程组的父子关系

ThreadLocal

ThreadLocal的类图如下

ThreadLocal类图

线程局部变量,为每一个使用变量的线程都提供一个变量值的副本,每个线程都可以独立地改变自己的副本,而不会和其他线程冲突。

ThreadLocal类有一个protected方法initialValue()和三个public方法get(),set(T),remove(),其他的私有方法暂不讨论,主要看一下3个公有方法的实现原理:

set(T)

    public void set(T value) {
        Thread t = Thread.currentThread();
        # 每个线程有自己维护的一个ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        # 如果存在就把当前值放入线程的Map中,key为当前ThreadLocal类的实例
        if (map != null)
            map.set(this, value);
        else
        # 如果不存在就创建这个Map,然后将ThreadLocal实例作为key
            createMap(t, value);
    }

T get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        # 根据当前的ThreadLocal实例获得存入的value值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        # 如果不存在就返回一个初始值
        return setInitialValue();
    }

remove()

    public void remove() {
         # 获得当前线程的Map
         ThreadLocalMap m = getMap(Thread.currentThread());
         # 根据当前ThreadLocal实例移除Map中的值
         if (m != null)
             m.remove(this);
     }

ThreadLocal(以空间换时间)和线程同步机制(以时间换空间)都是为了解决多线程中相同变量的访问冲突问题。

ThreadLocalMap

ThreadLocalMap实现了散列表,用来保存不同线程的信息,与HashMap解决哈希冲突的方式不一样。HashMap采用的拉链法,即当地址发生冲突时采用链表的方式,java8之后,当链表长度超过8之后转换为红黑树;ThreadLocalMap采用的开放地址法,即当地址冲突时不采用链表的方式,然后在数组中向后寻找空闲地址

InheritableThreadLocal

从上面已经知道ThreadLocal主要是依据Thread.currentThread()拿到线程的ThreadLocalMap变量,进而得到之前set的值。但是在某些情况下,需要在线程中获得其他线程set的值,就可以使用InheritableThreadLocal,从名字看出来这个就是用来继承的。

Thread类中存在两个变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

对于一个线程而言,threadLocals是给自己用的,而inheritableThreadLocals是给其子线程使用的。

查看new Thread()的源码:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
     ......
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;
    ......
    }

解释:线程A中新建一个子线程B,这时会判断线程A的inheritableThreadLocals的变量是否为null,如果不为空,就将线程A的inheritableThreadLocals变量赋给线程B的inheritableThreadLocals。

再看InheritableThreadLocal类的源码:

protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

其实和ThreadLocal中的方法差不多,起主要作用的是重写的getMap(Thread)方法,获得的是inheritableThreadLocals。

线程的生命周期

根据线程的状态转换图,主要看下面几个方法:start()、yield()、sleep()、join()、interrupt()、stop(),另外一些由于设计缺陷已经标记删除的方法不做分析。

start

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

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                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();

解释:当线程状态为0,即为NEW时才能调用start()方法,star()t方法主要是将该线程加入线程组,然后采用Native方法去真正启动一个线程,给线程分配资源,当线程得到CPU执行权的时候就开始执行任务,实际上就是调用实现了Runnable接口的任务的run()方法。最后当线程失败的时候,使用所有线程组的异常处理方式统一处理。

启动线程的方式:

  1. 因为Thread本身就实现了Runnable接口,因此可以直接继承Thread类,然后调用start()方法启动一个线程
  2. 实现Rannable接口,然后通过Thread()构造函数将其封装为线程,然后调用start()方法启动一个线程
  3. 实现Callable接口,然后通过ExecutorService的submit方法去启动一个线程

yield

当线程获得CPU执行权时状态变为运行,此时调用yield()方法可以释放CPU的执行权,回到就绪状态。

public static native void yield();

sleep

当线程在运行过程中调用sleep方法线程从运行状态变为阻塞状态,Object类中wait()方法也可以将线程从运行状态变为阻塞状态,不同的地方是wait()方法会释放同步锁,而sleep()方法不会释放同步锁

public static native void sleep(long millis) throws InterruptedException;

join

join方法可以让两个异步线程执行同步操作,即让其中一个阻塞到另外一个线程执行完

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

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

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

解释:join()方法其实就是调用了wait()方法去阻塞了调用该方法的线程,例如在线程A中调用了线程B的join方法,b对象成为了锁,线程A被阻塞,通过对象b的isAlive()判断线程B是否还在运行,知道线程B执行完毕,线程A才能继续执行

当sleep()方法的时间到,或者调用Object类的notify()或者notifyAll()方法,线程状态会从阻塞再次变为就绪

interrupt

interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
        return isInterrupted(false);
    }

private native boolean isInterrupted(boolean ClearInterrupted);

解释:interrupt0()方法仅仅是给线程设置一个中断标志,interrupted()和isInterrupted()方法都是用来判断线程的中断状态的,不同的是interrupted()会清空中断标志,将其恢复成false,但是isInterrupted()不会

如何中断线程

如果一个线程处于了阻塞状态(如线程调用了sleep、join、wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。

注意,synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

Runnable

Thread类实现Runnable接口,这个接口中只有一个run()方法,调用了线程start()方法后,当线程获得CPU执行权之后就会执行具体任务的run方法。

run()和start()的区别:

  1. run()只是一个普通的方法,并不会启动一个线程,具体的任务还是在当前线程中执行
  2. start()方法才是真正的启动一个线程

Callable

Callable接口中只存在一个call方法,与run方法不一样的是call方法会抛出异常,具有返回结果,它是JUC包中的一个接口,主要是采用Future模式,具体的详情在下一个章节介绍。

猜你喜欢

转载自blog.csdn.net/aimomo007/article/details/89328701