JAVA多线程之Thread源码解析

概述

什么是线程?

线程(英语:thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。–维基百科

JDK1.8中对Tread的定义:

Thread是程序中执行的线程。 Java虚拟机允许应用程序同时执行多个执行线程。

线程属性
ID

线程的id是线程的唯一标识符,无法手动设置,由线程中的init()方法赋予初始值,类似人类的身份证号码,伴随线程的整个生命周期。

    /*
     * Thread ID
     */
    private long tid;
 	/*
 	*获取线程id的方法
 	*/
    public long getId() {
        return tid;
    }
	/*
	*私有方法,id递增,init()方法赋予初始的值由该方法提供
	*/
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
名字

每一个线程都是有名字的,在Thread类中被定义为:

 private volatile String name;

通过getName(),可以获得线程的名字:

public final String getName() {
        return name;
    }

可以显式地通过setName()来设置线程的名字:

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

如果不显式设置名字,Thread类会通过构造器赋予初始值:

 public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
优先级
private int   priority;

Thread通过priority来定义线程的优先级,JDK中默认提供了三个优先级:

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;
   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;
    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

优先级越高,JVM执行该线程的可能性越大,对,仅仅只是可能性,类似于JAVA的GC机制,线程的执行同样是不可控的,依赖于操作系统,事实上,这意味着开发的简化与错误的减少,实际的开发中,一般使用默认优先级–NORM_PRIORITY。
Thread中,提供了设置与获取priority的方法,priority的设置不能大于10,不能小于1,否则将会抛出IllegalArgumentException:

	//设置Thread优先级
    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
    //获取Thread优先级
    public final int getPriority() {
        return priority;
    }
状态

在java中,线程状态用threadStatus表示:

private volatile int threadStatus = 0;

在操作系统中,线程的状态常常被分为创建、就绪、运行、阻塞(等待)、退出。JAVA里明确定义了Thread的六种状态

    public enum State {
        //新建,
        //Thread实例化后,未曾调用start方法
        NEW,

        //可运行,在虚拟机中正在执行或者可执行(等待CPU时间片)
        //调用start()
        RUNNABLE,

        //阻塞(等待monitor锁)
        //与其他线程竞争锁失败
        //使用Object.wait()释放锁后(唤醒后竞争锁失败)
        BLOCKED,

        //等待
        //调用Object.wait()
        //调用Thread.join 值得注意的是这里是让主线程陷入WAITING状态
        //调用LockSupport.park
        WAITING,

        //限时等待
        //调用Thread.sleep(long)
        //调用Object.wait(long)
        //调用Thread.join(long) 值得注意的是这里是让主线程陷入TIMED_WAITING状态
        //调用LockSupport.parkNanos
        //调用LockSupport.parkUntil
        TIMED_WAITING,

        //终止
        //线程执行完毕        
        TERMINATED;
    }

这里,用两个Demo来观察Thread状态的转换, 例一,线程从创建到消亡:

/**
 * @program: thinking-in-all
 * @description: 线程的NEW Runnable(可用状态 包括就绪 正在运行) Terminated(终止状态)
 * @author: Lucifinil
 * @create: 2019-11-10
 **/
public class NewRunnableTerminated implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i==5) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i);
        }
    }
    public static void main(String[] args) {
        Thread thread = new Thread(new NewRunnableTerminated());
        System.out.println("实例化之后的Thread状态:"+thread.getState());
        thread.start();
        System.out.println("调用start()方法之后的Thread状态:"+thread.getState());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行完毕之后的Thread状态:"+thread.getState());
    }
}

结果如下:

实例化之后的Thread状态:NEW
调用start()方法之后的Thread状态:RUNNABLE
0
1
2
3
4
5
6
7
8
9
执行完毕之后的Thread状态:TERMINATED

例二,线程的阻塞与等待:

/**
 * @program: thinking-in-all
 * @description: 线程的Blocked Waiting TimedWaiting
 * @author: Lucifinil
 * @create: 2019-11-10
 **/
public class BlockedWaitingTimedWaiting implements Runnable {
    @Override
    public void run() {
        syn();
    }
    public static void main(String[] args) {
        BlockedWaitingTimedWaiting runnable = new BlockedWaitingTimedWaiting();
        Thread thread1 = new Thread(runnable);
        thread1.start();
        //等待一段时间,否则thread2先于Thread1执行
        //此时第一次打印时thread1将陷入阻塞,后续thread2与thread1皆为TIMED_WAITING状态
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread thread2 = new Thread(runnable);
        thread2.start();
        //应当等待一段时间 否则主线程的打印可能会先于子线程执行
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread1:" + thread1.getState());
        System.out.println("thread2:" + thread2.getState());
        try {
            Thread.sleep(1300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread1:" + thread1.getState());
    }
    private synchronized void syn() {
        try {
            Thread.sleep(1000);
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果如下:

thread1:TIMED_WAITING
thread2:BLOCKED
thread1:WAITING

上面的两个例子,已经基本能说明线程之间的转换关系,那么线程的状态在底层是怎么转换的呢,其实JAVA的代码并没有显式地介绍如何改变状态,不过,toThreadState()方法表明了threadStatus与State中定义的六种状态的联系,其具体变化是在JVM中实现的,这里暂不作讨论:

    public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }
守护线程

JDK1.8中提到

当在某个线程中运行的代码创建一个新的Thread对象时,当且仅当创建线程是守护进程时才是守护线程。

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。所谓守护线程,可以理解为一个服务的线程,当任意非守护线程存在时,守护线程都不会销毁,像不像默默采蜜的工蜂,只要蜂后(用户线程)存活,工蜂便会一直工作。Java中最经典的守护线程,也是大家常常拿来举的例子–GC垃圾回收,在后台为我们清理无用内存,其实JVM在启动的时候,肯定不止GC一个守护线程,更何况,我们有时候也会定义我们自己的守护线程:

 private boolean  daemon = false;

Java提供了两个方法来支持daemon的获取与设置:

    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

    public final boolean isDaemon() {
        return daemon;
    }

设置守护线程时,会进行使用checkAccess()判断当前线程是否有权修改目标线程,这个是Java中的安全机制,这里暂时不作深究,目标线程不能是存活状态,否则将抛出IllegalThreadStateException,不是所有的线程都能设置为守护线程,守护线程可能会还未执行,Jvm便已经退出,所以,一些必须完成的任务不能使用守护线程。
用一个例子演示守护线程:

/**
 * @program: thinking-in-all
 * @description: 守护线程的创建与运行
 * @author: Lucifinil
 * @create: 2019-12-13
 **/
public class DaemonDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("我是守护线程.");
    }
    public static void main(String[] args) {
        Thread daemon = new Thread(new DaemonDemo());
        daemon.setDaemon(true);
        daemon.start();
    }
}

结果如下

我是守护线程.

或者


线程的执行由Jvm控制,在主线程执行结束,Jvm停止之前,守护线程可能会执行,也可能不会执行,这个和守护线程的执行频率和主线程的执行时间有关。

线程组
 /* The group of this thread */
    private ThreadGroup group;

Java用ThreadGroup类来表明当前Thread属于哪一个线程组,线程组表示一组线程。

线程组还可以包括其他线程组。线程组形成一个树,其中除了初始线程组之外的每个线程组都有一个父线程组。
允许线程访问关于其线程组的信息,但不允许访问关于其线程组的父线程组或任何其他线程组的信息。

Thread类中提供构造器可以将ThreadGroup 作为参数传入,这是自定义的线程组,如果没有显式声明,该线程属于默认线程组,也就是主线程所在的线程组:

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        //略
    }

Thread类提供了getThreadGroup()方法会获取线程的线程组:

    public final ThreadGroup getThreadGroup() {
        return group;
    }

显而易见的是,一个线程只能属于一个线程组。线程组有什么好处呢?前文提到过 checkAccess()方法,该方法是通过Java中的SecurityManager来进行安全策略的管理,而线程组中的线程便可以配置SecurityManager来进行鉴权,只有同一个线程组的线程才有修改彼此数据的权限,这是其安全方面的考量。另外,在ThreadGroup类 中还提供了批量管理线程的方法,这不是本文讨论的重点,在此便不作深究。

线程局部变量
 /* 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;

通过ThreadLocal类与InheritableThreadLocal类可以操纵Thread的threadLocals 与inheritableThreadLocals ,隔离并发环境下线程对数据的访问冲突,后续会详细介绍。

访问控制上下文
private AccessControlContext inheritedAccessControlContext;

AccessControlContext用于根据其封装的上下文来进行系统资源访问决策。 更具体地,它封装了一个上下文,并且具有单一的方法, checkPermission ,即相当于checkPermission在AccessController类方法,有一点不同:所述的AccessControlContext checkPermission方法使得访问决策基于它所封装的上下文,而不是的当前执行线程。

这是关于java有关安全策略和权限控制的类,根据当前的AccessControlContext和安全策略可以确定是否允许或拒绝由指定权限指定的访问请求。
简单的讲,访问控制上下文中封装了一组权限,以决定当前线程可以访问哪些资源。
该值不能为空,通过构造器可以显式设置该值,如果不显式地设置,该值会通AccessController.getContext()从上下文中获取。

线程上下文类加载器
    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;

线程上下文类加载器是在J2SE中被引进的,每一个线程分配一个上下文类加载器(除非线程由本地代码创建)。该加载器是通过Thread.setContextClassLoader()方法来设置。如果你在线程构造后不调用这个方法,这个线程将会从它的父线程(执行创建新线程对象语句的线程)中继承上下文类加载器。如果你在整个应用中不做任何设置,所有线程将以系统类加载器作为它们自己的上下文加载器。
所谓的线程上下文类加载器的目的在于,打破类的双亲委托机制,因为某些场景下,该机制无法正常使用,这里不对类加载作深究。
Tread类提供了两个方法对contextClassLoader进行获取与设置:


    public void setContextClassLoader(ClassLoader cl) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("setContextClassLoader"));
        }
        contextClassLoader = cl;
    }

    @CallerSensitive
    public ClassLoader getContextClassLoader() {
        if (contextClassLoader == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                   Reflection.getCallerClass());
        }
        return contextClassLoader;
    }
任务

Java中使用Runnable接口定义任务:

    /* What will be run. */
    private Runnable target;

任务的执行是run(),run()的显式调用虽然能够执行,但那并不是多线程下的任务,调用start()方法后,Jvm会启动一个新的子线程并对run()进行回调,此时的多线程才有意义,:

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

这里针对一些重要方法进行介绍:

currentThread()
public static native Thread currentThread();

jdk1.8中对该方法的定义为:返回对当前正在执行的线程对象的引用。 简而言之,便是获取当前正在执行的线程,这是一个静态方法,通过Thread类来调用。

yield()
public static native void yield();

yield在英文中的含义有让步的意思,所谓的让步是指当前线程放弃CPU时间片,以提供给其他线程执行的机会。
yield()不释放锁,且不改变线程状态,当前线程状态仍然为Runnable,执行yield()之后,当前线程仍有可能继续执行。
关于yield()的使用场景,jdk1.8中提到:

It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the {@link java.util.concurrent.locks} package.

大概是说,yield()使用场景不多,可以在快速轮询,开发调试和程序并发控制等场景下使用。

sleep()
   public static native void sleep(long millis) throws InterruptedException;
   
   public static void sleep(long millis, int nanos)
    throws InterruptedException {
        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++;
        }

        sleep(millis);
    }

sleep()被static和native修饰,jdk1.8中提到,sleep()使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 线程不会丢失任何监视器的所有权。
sleep()必须传入一个大于等于0的值,代表毫秒数,当该值小于0时,抛出异常,故而,sleep()不会释放锁
该方法常常拿来与Object.wait()对比,一个显著的区别便是slepp()不会释放锁

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

Thread的所有构造方法(包括无参构造)都是通过init()方法来给线程赋值,该方法具有六个参数:线程组,Runnale实例,线程名称,线程栈大小,访问控制上下文,是否继承父线程inheritableThreadLocals。

未完待续:)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值