Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

从源码的角度再学「Thread」

公众号_张少林同学 2019-01-21 10:55:00 阅读数:139 评论数:0 点赞数:0 收藏数:0

前言

Java中的线程是使用

Thread类实现的,

Thread在初学

Java的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习

Java Thread,愿此后对

Thread的实践更加得心应手。

从注释开始

相信阅读过

JDK源码的同学都能感受到

JDK源码中有非常详尽的注释,阅读某个类的源码应当先看看注释对它的介绍,注释原文就不贴了,以下是我对它的总结:

  • Thread
    是程序中执行的线程,

Java虚拟机允许应用程序同时允许多个执行线程

  • 每个线程都有优先级的概念,具有较高优先级的线程优先于优先级较低的线程执行
  • 每个线程都可以被设置为守护线程
  • 当在某个线程中运行的代码创建一个新的

Thread对象时,新的线程优先级跟创建线程一致

Java虚拟机启动的时候都会启动一个叫做

main的线程,它没有守护线程,

main线程会继续执行,直到以下情况发送

  • Runtime
    类的退出方法

exit被调用并且安全管理器允许进行退出操作

  • 所有非守护线程均已死亡,或者

run方法执行结束正常返回结果,或者

run方法抛出异常

  • 创建线程第一种方式:继承

Thread类,重写

run方法1//定义线程类 2class PrimeThread extends Thread { 3      long minPrime; 4      PrimeThread(long minPrime) { 5          this.minPrime = minPrime; 6      } 7      public void run() { 8          // compute primes larger than minPrime 9           . . . 10      } 11  } 12//启动线程 13PrimeThread p = new PrimeThread(143); 14p.start();

  • 创建线程第二种方式:实现

Runnable接口,重写

run方法,因为

Java的单继承限制,通常使用这种方式创建线程更加灵活1//定义线程 2 class PrimeRun implements Runnable { 3      long minPrime; 4      PrimeRun(long minPrime) { 5          this.minPrime = minPrime; 6      } 7      public void run() { 8          // compute primes larger than minPrime 9           . . . 10      } 11  } 12//启动线程 13PrimeRun p = new PrimeRun(143); 14new Thread(p).start();

  • 创建线程时可以给线程指定名字,如果没有指定,会自动为它生成名字
  • 除非另有说明,否则将

null参数传递给

Thread类中的构造函数或方法将导致抛出

NullPointerException

Thread 常用属性

阅读一个

Java类,先从它拥有哪些属性入手:1//线程名称,创建线程时可以指定线程的名称 2private volatile String name; 3 4//线程优先级,可以设置线程的优先级 5private int priority; 6 7//可以配置线程是否为守护线程,默认为false 8private boolean daemon = false; 9 10//最终执行线程任务的Runnable 11private Runnable target; 12 13//描述线程组的类 14private ThreadGroup group; 15 16//此线程的上下文ClassLoader 17private ClassLoader contextClassLoader; 18 19//所有初始化线程的数目,用于自动编号匿名线程,当没有指定线程名称时,会自动为其编号 20private static int threadInitNumber; 21 22//此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。, 虚拟机可以用这个数字做任何喜欢的事情。, 一些虚拟机会忽略它。 23private long stackSize; 24 25//线程id 26private long tid; 27 28//用于生成线程ID 29private static long threadSeqNumber; 30 31//线程状态 32private volatile int threadStatus = 0; 33 34//线程可以拥有的最低优先级 35public final static int MINPRIORITY = 1; 36 37//分配给线程的默认优先级。 38public final static int NORMPRIORITY = 5; 39 40//线程可以拥有的最大优先级 41public final static int MAX_PRIORITY = 10;

所有的属性命名都很语义化,其实已看名称基本就猜到它是干嘛的了,难度不大~~

Thread 构造方法

了解了属性之后,看看

Thread实例是怎么构造的?先预览下它大致有多少个构造方法:

查看每个构造方法内部源码,发现均调用的是名为

init的私有方法,再看

init方法有两个重载,而其核心方法如下:

1   /// 2     / Initializes a Thread. 3     / 4     / @param g                   线程组 5     / @param target              最终执行任务的 run() 方法的对象 6     / @param name                新线程的名称 7     / @param stackSize           新线程所需的堆栈大小,或者 0 表示要忽略此参数 8     / @param acc                 要继承的AccessControlContext,如果为null,则为 AccessController.getContext() 9     / @param inheritThreadLocals 如果为 true,从构造线程继承可继承的线程局部的初始值 10     /*/ 11    private void init(ThreadGroup g, Runnable target, String name, 12                      long stackSize, AccessControlContext acc, 13                      boolean inheritThreadLocals) { 14        //线程名称为空,直接抛出空指针异常 15        if (name == null) { 16            throw new NullPointerException("name cannot be null"); 17        } 18        //初始化当前线程对象的线程名称 19        this.name = name; 20        //获取当前正在执行的线程为父线程 21        Thread parent = currentThread(); 22        //获取系统安全管理器 23        SecurityManager security = System.getSecurityManager(); 24        //如果线程组为空 25        if (g == null) { 26            //如果安全管理器不为空 27            if (security != null) { 28                //获取SecurityManager中的线程组 29                g = security.getThreadGroup(); 30            } 31            //如果获取的线程组还是为空 32            if (g == null) { 33                //则使用父线程的线程组 34                g = parent.getThreadGroup(); 35            } 36        } 37 38        //检查安全权限 39        g.checkAccess(); 40 41        //使用安全管理器检查是否有权限 42        if (security != null) { 43            if (isCCLOverridden(getClass())) { 44                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); 45            } 46        } 47 48        //线程组中标记未启动的线程数+1,这里方法是同步的,防止出现线程安全问题 49        g.addUnstarted(); 50 51        //初始化当前线程对象的线程组 52        this.group = g; 53        //初始化当前线程对象的是否守护线程属性,注意到这里初始化时跟父线程一致 54        this.daemon = parent.isDaemon(); 55        //初始化当前线程对象的线程优先级属性,注意到这里初始化时跟父线程一致 56        this.priority = parent.getPriority(); 57        //这里初始化类加载器 58        if (security == null || isCCLOverridden(parent.getClass())) 59            this.contextClassLoader = parent.getContextClassLoader(); 60        else 61            this.contextClassLoader = parent.contextClassLoader; 62        this.inheritedAccessControlContext = 63                acc != null ? acc : AccessController.getContext(); 64        //初始化当前线程对象的最终执行任务对象 65        this.target = target; 66        //这里再对线程的优先级字段进行处理 67        setPriority(priority); 68        if (inheritThreadLocals && parent.inheritableThreadLocals != null) 69            this.inheritableThreadLocals = 70                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 71        //初始化当前线程对象的堆栈大小 72        this.stackSize = stackSize; 73 74        //初始化当前线程对象的线程ID,该方法是同步的,内部实际上是threadSeqNumber++ 75        tid = nextThreadID(); 76    }

另一个重载

init私有方法如下,实际上内部调用的是上述

init方法:

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

接下来看看所有构造方法:

1.空构造方法1 public Thread() { 2        init(null, null, "Thread-" + nextThreadNum(), 0); 3    }

内部调用的是

init第二个重载方法,参数基本都是默认值,线程名称写死为

"Thread-" + nextThreadNum()格式,

nextThreadNum()为一个同步方法,内部维护一个静态属性表示线程的初始化数量+1:

1 private static int threadInitNumber; 2    private static synchronized int nextThreadNum() { 3        return threadInitNumber++; 4    }1.自定义执行任务

Runnable对象的构造方法1public Thread(Runnable target) { 2    init(null, target, "Thread-" + nextThreadNum(), 0); 3}

与第一个构造方法区别在于可以自定义

Runnable对象1.自定义执行任务

Runnable对象和

AccessControlContext对象的构造方法1 Thread(Runnable target, AccessControlContext acc) { 2    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); 3}1.自定义线程组

ThreadGroup和执行任务

Runnable对象的构造方法1public Thread(ThreadGroup group, Runnable target) { 2    init(group, target, "Thread-" + nextThreadNum(), 0); 3}1.自定义线程名称

name的构造方法1 public Thread(String name) { 2    init(null, null, name, 0); 3}1.自定义线程组

ThreadGroup和线程名称

name的构造方法1 public Thread(ThreadGroup group, String name) { 2    init(group, null, name, 0); 3}1.自定义执行任务

Runnable对象和线程名称

name的构造方法1 public Thread(Runnable target, String name) { 2    init(null, target, name, 0); 3}1.自定义线程组

ThreadGroup和线程名称

name和执行任务

Runnable对象的构造方法1  public Thread(ThreadGroup group, Runnable target, String name) { 2    init(group, target, name, 0); 3}1.全部属性都是自定义的构造方法1  public Thread(ThreadGroup group, Runnable target, String name, 2              long stackSize) { 3    init(group, target, name, stackSize); 4}

Thread提供了非常灵活的重载构造方法,方便开发者自定义各种参数的

Thread对象。

常用方法

这里记录一些比较常见的方法吧,对于

Thread中存在的一些本地方法,我们暂且不用管它~

设置线程名称

设置线程名称,该方法为同步方法,为了防止出现线程安全问题,可以手动调用

Thread的实例方法设置名称,也可以在构造

Thread时在构造方法中传入线程名称,我们通常都是在构造参数时设置1   public final synchronized void setName(String name) { 2        //检查安全权限 3          checkAccess(); 4        //如果形参为空,抛出空指针异常 5          if (name == null) { 6              throw new NullPointerException("name cannot be null"); 7          } 8        //给当前线程对象设置名称 9          this.name = name; 10          if (threadStatus != 0) { 11              setNativeName(name); 12          } 13      }

获取线程名称

内部直接返回当前线程对象的名称属性1  public final String getName() { 2        return name; 3    }

启动线程

1public synchronized void start() { 2        //如果不是刚创建的线程,抛出异常 3        if (threadStatus != 0) 4            throw new IllegalThreadStateException(); 5 6        //通知线程组,当前线程即将启动,线程组当前启动线程数+1,未启动线程数-1 7        group.add(this); 8 9        //启动标识 10        boolean started = false; 11        try { 12            //直接调用本地方法启动线程 13            start0(); 14            //设置启动标识为启动成功 15            started = true; 16        } finally { 17            try { 18                //如果启动呢失败 19                if (!started) { 20                    //线程组内部移除当前启动的线程数量-1,同时启动失败的线程数量+1 21                    group.threadStartFailed(this); 22                } 23            } catch (Throwable ignore) { 24                // do nothing. If start0 threw a Throwable then 25                  it will be passed up the call stack // 26            } 27        } 28    }

我们正常的启动线程都是调用

Thread的

start()方法,然后

Java虚拟机内部会去调用

Thred的

run方法,可以看到

Thread类也是实现

Runnable接口,重写了

run方法的:

1 @Override 2    public void run() { 3        //当前执行任务的Runnable对象不为空,则调用其run方法 4        if (target != null) { 5            target.run(); 6        } 7    }

Thread的两种使用方式:

  • 继承

Thread类,重写

run方法,那么此时是直接执行

run方法的逻辑,不会使用

target.run();

  • 实现

Runnable接口,重写

run方法,因为

Java的单继承限制,通常使用这种方式创建线程更加灵活,这里真正的执行逻辑就会交给自定义

Runnable去实现

设置守护线程

本质操作是设置

daemon属性1public final void setDaemon(boolean on) { 2        //检查是否有安全权限 3        checkAccess(); 4        //本地方法,测试此线程是否存活。, 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态 5        if (isAlive()) { 6            //如果线程先启动后再设置守护线程,将抛出异常 7            throw new IllegalThreadStateException(); 8        } 9        //设置当前守护线程属性 10        daemon = on; 11    }

判断线程是否为守护线程

1 public final boolean isDaemon() { 2        //直接返回当前对象的守护线程属性 3        return daemon; 4    }

线程状态

先来个线程状态图:

获取线程状态:

1 public State getState() { 2        //由虚拟机实现,获取当前线程的状态 3        return sun.misc.VM.toThreadState(threadStatus); 4    }

线程状态主要由内部枚举类

State组成:

1  public enum State { 2 3        NEW, 4 5 6        RUNNABLE, 7 8 9        BLOCKED, 10 11 12        WAITING, 13 14 15        TIMED_WAITING, 16 17 18        TERMINATED; 19    }

  • NEW:刚刚创建,尚未启动的线程处于此状态
  • RUNNABLE:在Java虚拟机中执行的线程处于此状态
  • BLOCKED:被阻塞等待监视器锁的线程处于此状态,比如线程在执行过程中遇到

synchronized同步块,就会进入此状态,此时线程暂停执行,直到获得请求的锁

  • WAITING:无限期等待另一个线程执行特定操作的线程处于此状态
  • 通过 wait() 方法等待的线程在等待 notify() 方法
  • 通过 join() 方法等待的线程则会等待目标线程的终止
  • TIMED_WAITING:正在等待另一个线程执行动作,直到指定等待时间的线程处于此状态
  • 通过 wait() 方法,携带超时时间,等待的线程在等待 notify() 方法
  • 通过 join() 方法,携带超时时间,等待的线程则会等待目标线程的终止
  • TERMINATED:已退出的线程处于此状态,此时线程无法再回到 RUNNABLE 状态

线程休眠

这是一个静态的本地方法,使当前执行的线程休眠暂停执行

millis毫秒,当休眠被中断时会抛出

InterruptedException中断异常1    /// 2     / Causes the currently executing thread to sleep (temporarily cease 3     / execution) for the specified number of milliseconds, subject to 4     / the precision and accuracy of system timers and schedulers. The thread 5     / does not lose ownership of any monitors. 6     / 7     / @param  millis 8     /         the length of time to sleep in milliseconds 9     / 10     / @throws  IllegalArgumentException 11     /          if the value of {@code millis} is negative 12     / 13     / @throws  InterruptedException 14     /          if any thread has interrupted the current thread. The 15     /          interrupted status of the current thread is 16     /          cleared when this exception is thrown. 17     // 18    public static native void sleep(long millis) throws InterruptedException;

检查线程是否存活

本地方法,测试此线程是否存活。 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态。1    /// 2     / Tests if this thread is alive. A thread is alive if it has 3     / been started and has not yet died. 4     / 5     / @return  true if this thread is alive; 6     /          false otherwise. 7     // 8    public final native boolean isAlive();

线程优先级

  • 设置线程优先级
    1    /// 2     / Changes the priority of this thread. 3     / 

    4     / First the checkAccess method of this thread is called 5     / with no arguments. This may result in throwing a 6     / SecurityException. 7     / 

    8     / Otherwise, the priority of this thread is set to the smaller of 9     / the specified newPriority and the maximum permitted 10     / priority of the thread's thread group. 11     / 12     / @param newPriority priority to set this thread to 13     / @exception  IllegalArgumentException  If the priority is not in the 14     /               range MIN_PRIORITY to 15     /               MAXPRIORITY. 16     / @exception  SecurityException  if the current thread cannot modify 17     /               this thread. 18     / @see        /#getPriority 19     / @see        /#checkAccess() 20     / @see        /#getThreadGroup() 21     / @see        /#MAXPRIORITY 22     / @see        /#MIN_PRIORITY 23     / @see        ThreadGroup/#getMaxPriority() 24     /*/ 25    public final void setPriority(int newPriority) { 26        //线程组 27        ThreadGroup g; 28        //检查安全权限 29        checkAccess(); 30        //检查优先级形参范围 31        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { 32            throw new IllegalArgumentException(); 33        } 34        if((g = getThreadGroup()) != null) { 35            //如果优先级形参大于线程组最大线程最大优先级 36            if (newPriority > g.getMaxPriority()) { 37                //则使用线程组的优先级数据 38                newPriority = g.getMaxPriority(); 39            } 40            //调用本地设置线程优先级方法 41            setPriority0(priority = newPriority); 42        } 43    }

线程中断

有一个

stop()实例方法可以强制终止线程,不过这个方法因为太过于暴力,已经被标记为过时方法,不建议程序员再使用,因为强制终止线程会导致数据不一致的问题。

这里关于线程中断的方法涉及三个:1//实例方法,通知线程中断,设置标志位 2 public void interrupt(){} 3 //静态方法,检查当前线程的中断状态,同时会清除当前线程的中断标志位状态 4 public static boolean interrupted(){} 5 //实例方法,检查当前线程是否被中断,其实是检查中断标志位 6 public boolean isInterrupted(){}

interrupt() 方法解析

1/// 2     / Interrupts this thread. 3     / 4     / 

 Unless the current thread is interrupting itself, which is 5     / always permitted, the {@link /#checkAccess() checkAccess} method 6     / of this thread is invoked, which may cause a {@link 7     / SecurityException} to be thrown. 8     / 9     / 

 If this thread is blocked in an invocation of the {@link 10     / Object/#wait() wait()}, {@link Object/#wait(long) wait(long)}, or {@link 11     / Object/#wait(long, int) wait(long, int)} methods of the {@link Object} 12     / class, or of the {@link /#join()}, {@link /#join(long)}, {@link 13     / /#join(long, int)}, {@link /#sleep(long)}, or {@link /#sleep(long, int)}, 14     / methods of this class, then its interrupt status will be cleared and it 15     / will receive an {@link InterruptedException}. 16     / 17     / 

 If this thread is blocked in an I/O operation upon an {@link 18     / java.nio.channels.InterruptibleChannel InterruptibleChannel} 19     / then the channel will be closed, the thread's interrupt 20     / status will be set, and the thread will receive a {@link 21     / java.nio.channels.ClosedByInterruptException}. 22     / 23     / 

 If this thread is blocked in a {@link java.nio.channels.Selector} 24     / then the thread's interrupt status will be set and it will return 25     / immediately from the selection operation, possibly with a non-zero 26     / value, just as if the selector's {@link 27     / java.nio.channels.Selector/#wakeup wakeup} method were invoked. 28     / 29     / 

 If none of the previous conditions hold then this thread's interrupt 30     / status will be set. 

31     / 32     / 

 Interrupting a thread that is not alive need not have any effect. 33     / 34     / @throws  SecurityException 35     /          if the current thread cannot modify this thread 36     / 37     / @revised 6.0 38     / @spec JSR-51 39     // 40    public void interrupt() { 41        //检查是否是自身调用 42        if (this != Thread.currentThread()) 43            //检查安全权限,这可能导致抛出{@link /* SecurityException}。 44            checkAccess(); 45 46        //同步代码块 47        synchronized (blockerLock) { 48            Interruptible b = blocker; 49            //检查是否是阻塞线程调用 50            if (b != null) { 51                //设置线程中断标志位 52                interrupt0();  53                //此时抛出异常,将中断标志位设置为false,此时我们正常会捕获该异常,重新设置中断标志位 54                b.interrupt(this); 55                return; 56            } 57        } 58        //如无意外,则正常设置中断标志位 59        interrupt0(); 60    }

  • 线程中断方法不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦~
  • 只能由自身调用,否则可能会抛出

SecurityException

  • 调用中断方法是由目标线程自己决定是否中断,而如果同时调用了

wait,

join,

sleep等方法,会使当前线程进入阻塞状态,此时有可能发生

InterruptedException异常

  • 被阻塞的线程再调用中断方法是不合理的
  • 中断不活动的线程不会产生任何影响

检查线程是否被中断:1    /// 2     / Tests whether this thread has been interrupted.  The interrupted 3     / status of the thread is unaffected by this method. 4 5     测试此线程是否已被中断。, 线程的中断/状态不受此方法的影响。 6     / 7     / 

A thread interruption ignored because a thread was not alive 8     / at the time of the interrupt will be reflected by this method 9     / returning false. 10     / 11     / @return  true if this thread has been interrupted; 12     /          false otherwise. 13     / @see     /#interrupted() 14     / @revised 6.0 15     /*/ 16    public boolean isInterrupted() { 17        return isInterrupted(false); 18    }

静态方法,会清空当前线程的中断标志位:

1   /// 2     /测试当前线程是否已被中断。, 此方法清除线程的/ 中断状态。, 换句话说,如果要连续两次调用此方法,则/ second调用将返回false(除非当前线程再次被中断,在第一次调用已清除其中断的/状态   之后且在第二次调用已检查之前), 它) 3     / 4     / 

A thread interruption ignored because a thread was not alive 5     / at the time of the interrupt will be reflected by this method 6     / returning false. 7     / 8     / @return  true if the current thread has been interrupted; 9     /          false otherwise. 10     / @see /#isInterrupted() 11     / @revised 6.0 12     // 13    public static boolean interrupted() { 14        return currentThread().isInterrupted(true); 15    }

总结

记录自己阅读

Thread类源码的一些思考,不过对于其中用到的很多本地方法只能望而却步,还有一些代码没有看明白,暂且先这样吧,如果有不足之处,请留言告知我,谢谢!后续会在实践中对

Thread做出更多总结记录。

最后

由于篇幅较长,暂且先记录这些吧,后续会不定期更新原创文章,欢迎关注公众号 「张少林同学」!

版权声明
本文为[公众号_张少林同学]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/zhangshaolin/p/10297676.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领