美文网首页Java 杂谈Java架构师
从源码的角度再学「Thread」

从源码的角度再学「Thread」

作者: Java耕耘者 | 来源:发表于2019-01-21 21:50 被阅读0次

前言

Java中的线程是使用Thread类实现的,Thread在初学Java的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习Java Thread,愿此后对Thread的实践更加得心应手。

从注释开始

相信阅读过JDK源码的同学都能感受到JDK源码中有非常详尽的注释,阅读某个类的源码应当先看看注释对它的介绍,注释原文就不贴了,以下是我对它的总结:

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

每个线程都有优先级的概念,具有较高优先级的线程优先于优先级较低的线程执行

每个线程都可以被设置为守护线程

当在某个线程中运行的代码创建一个新的Thread对象时,新的线程优先级跟创建线程一致

当Java虚拟机启动的时候都会启动一个叫做main的线程,它没有守护线程,main线程会继续执行,直到以下情况发送

Runtime类的退出方法exit被调用并且安全管理器允许进行退出操作

所有非守护线程均已死亡,或者run方法执行结束正常返回结果,或者run方法抛出异常

创建线程第一种方式:继承Thread类,重写run方法

//定义线程类classPrimeThreadextendsThread{longminPrime;      PrimeThread(longminPrime) {this.minPrime = minPrime;      }publicvoidrun(){// compute primes larger than minPrime . . .      }  }//启动线程PrimeThread p =newPrimeThread(143);p.start();

创建线程第二种方式:实现Runnable接口,重写run方法,因为Java的单继承限制,通常使用这种方式创建线程更加灵活

//定义线程classPrimeRunimplementsRunnable{longminPrime;      PrimeRun(longminPrime) {this.minPrime = minPrime;      }publicvoidrun(){// compute primes larger than minPrime . . .      }  }//启动线程PrimeRun p =newPrimeRun(143);newThread(p).start();

创建线程时可以给线程指定名字,如果没有指定,会自动为它生成名字

除非另有说明,否则将null参数传递给Thread类中的构造函数或方法将导致抛出NullPointerException

Thread 常用属性

阅读一个Java类,先从它拥有哪些属性入手:

//线程名称,创建线程时可以指定线程的名称privatevolatileString name;//线程优先级,可以设置线程的优先级privateintpriority;//可以配置线程是否为守护线程,默认为falseprivatebooleandaemon =false;//最终执行线程任务的`Runnable`privateRunnable target;//描述线程组的类privateThreadGroup group;//此线程的上下文ClassLoaderprivateClassLoader contextClassLoader;//所有初始化线程的数目,用于自动编号匿名线程,当没有指定线程名称时,会自动为其编号privatestaticintthreadInitNumber;//此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。, 虚拟机可以用这个数字做任何喜欢的事情。, 一些虚拟机会忽略它。privatelongstackSize;//线程idprivatelongtid;//用于生成线程IDprivatestaticlongthreadSeqNumber;//线程状态privatevolatileintthreadStatus =0;//线程可以拥有的最低优先级publicfinalstaticintMIN_PRIORITY =1;//分配给线程的默认优先级。publicfinalstaticintNORM_PRIORITY =5;//线程可以拥有的最大优先级publicfinalstaticintMAX_PRIORITY =10;

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

Thread 构造方法

了解了属性之后,看看Thread实例是怎么构造的?先预览下它大致有多少个构造方法:

查看每个构造方法内部源码,发现均调用的是名为init的私有方法,再看init方法有两个重载,而其核心方法如下:

/**    * Initializes a Thread.    *    *@paramg                  线程组    *@paramtarget              最终执行任务的 `run()` 方法的对象    *@paramname                新线程的名称    *@paramstackSize          新线程所需的堆栈大小,或者 0 表示要忽略此参数    *@paramacc                要继承的AccessControlContext,如果为null,则为 AccessController.getContext()    *@paraminheritThreadLocals 如果为 true,从构造线程继承可继承的线程局部的初始值    */privatevoidinit(ThreadGroup g, Runnable target, String name,longstackSize, AccessControlContext acc,booleaninheritThreadLocals){//线程名称为空,直接抛出空指针异常if(name ==null) {thrownewNullPointerException("name cannot be null");        }//初始化当前线程对象的线程名称this.name = name;//获取当前正在执行的线程为父线程Thread parent = currentThread();//获取系统安全管理器SecurityManager security = System.getSecurityManager();//如果线程组为空if(g ==null) {//如果安全管理器不为空if(security !=null) {//获取SecurityManager中的线程组g = security.getThreadGroup();            }//如果获取的线程组还是为空if(g ==null) {//则使用父线程的线程组g = parent.getThreadGroup();            }        }//检查安全权限g.checkAccess();//使用安全管理器检查是否有权限if(security !=null) {if(isCCLOverridden(getClass())) {                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);            }        }//线程组中标记未启动的线程数+1,这里方法是同步的,防止出现线程安全问题g.addUnstarted();//初始化当前线程对象的线程组this.group = g;//初始化当前线程对象的是否守护线程属性,注意到这里初始化时跟父线程一致this.daemon = parent.isDaemon();//初始化当前线程对象的线程优先级属性,注意到这里初始化时跟父线程一致this.priority = parent.getPriority();//这里初始化类加载器if(security ==null|| isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.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);//初始化当前线程对象的堆栈大小this.stackSize = stackSize;//初始化当前线程对象的线程ID,该方法是同步的,内部实际上是threadSeqNumber++tid = nextThreadID();    }

另一个重载init私有方法如下,实际上内部调用的是上述init方法:

privatevoidinit(ThreadGroup g, Runnable target, String name,longstackSize){        init(g, target, name, stackSize,null,true);    }

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

空构造方法

publicThread(){        init(null,null,"Thread-"+ nextThreadNum(),0);    }

内部调用的是init第二个重载方法,参数基本都是默认值,线程名称写死为"Thread-" + nextThreadNum()格式,nextThreadNum()为一个同步方法,内部维护一个静态属性表示线程的初始化数量+1:

privatestaticintthreadInitNumber;privatestaticsynchronizedintnextThreadNum(){returnthreadInitNumber++;    }

自定义执行任务Runnable对象的构造方法

publicThread(Runnable target){    init(null, target,"Thread-"+ nextThreadNum(),0);}

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

自定义执行任务Runnable对象和AccessControlContext对象的构造方法

Thread(Runnable target, AccessControlContext acc) {    init(null, target,"Thread-"+ nextThreadNum(),0, acc,false);}

自定义线程组ThreadGroup和执行任务Runnable对象的构造方法

publicThread(ThreadGroup group, Runnable target){    init(group, target,"Thread-"+ nextThreadNum(),0);}

自定义线程名称name的构造方法

publicThread(String name){    init(null,null, name,0);}

自定义线程组ThreadGroup和线程名称name的构造方法

publicThread(ThreadGroup group, String name){    init(group,null, name,0);}

自定义执行任务Runnable对象和线程名称name的构造方法

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

自定义线程组ThreadGroup和线程名称name和执行任务Runnable对象的构造方法

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

全部属性都是自定义的构造方法

publicThread(ThreadGroup group, Runnable target, String name,longstackSize){    init(group, target, name, stackSize);}

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

常用方法

这里记录一些比较常见的方法吧,对于Thread中存在的一些本地方法,我们暂且不用管它~

设置线程名称

设置线程名称,该方法为同步方法,为了防止出现线程安全问题,可以手动调用Thread的实例方法设置名称,也可以在构造Thread时在构造方法中传入线程名称,我们通常都是在构造参数时设置

publicfinalsynchronizedvoidsetName(String name){        //检查安全权限checkAccess();        //如果形参为空,抛出空指针异常if(name ==null) {thrownewNullPointerException("name cannot be null");          }//给当前线程对象设置名称this.name = name;if(threadStatus !=0) {              setNativeName(name);          }      }

获取线程名称

内部直接返回当前线程对象的名称属性

publicfinalStringgetName(){returnname;    }

启动线程

publicsynchronizedvoidstart(){//如果不是刚创建的线程,抛出异常if(threadStatus !=0)thrownewIllegalThreadStateException();//通知线程组,当前线程即将启动,线程组当前启动线程数+1,未启动线程数-1group.add(this);//启动标识booleanstarted =false;try{//直接调用本地方法启动线程start0();//设置启动标识为启动成功started =true;        }finally{try{//如果启动呢失败if(!started) {//线程组内部移除当前启动的线程数量-1,同时启动失败的线程数量+1group.threadStartFailed(this);                }            }catch(Throwable ignore) {/* do nothing. If start0 threw a Throwable then

                  it will be passed up the call stack */}        }    }

我们正常的启动线程都是调用Thread的start()方法,然后Java虚拟机内部会去调用Thred的run方法,可以看到Thread类也是实现Runnable接口,重写了run方法的:

@Overridepublicvoidrun(){//当前执行任务的Runnable对象不为空,则调用其run方法if(target !=null) {            target.run();        }    }

Thread的两种使用方式:

继承Thread类,重写run方法,那么此时是直接执行run方法的逻辑,不会使用target.run();

实现Runnable接口,重写run方法,因为Java的单继承限制,通常使用这种方式创建线程更加灵活,这里真正的执行逻辑就会交给自定义Runnable去实现

设置守护线程

本质操作是设置daemon属性

publicfinalvoidsetDaemon(booleanon){//检查是否有安全权限checkAccess();//本地方法,测试此线程是否存活。, 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态if(isAlive()) {//如果线程先启动后再设置守护线程,将抛出异常thrownewIllegalThreadStateException();        }//设置当前守护线程属性daemon = on;    }

判断线程是否为守护线程

publicfinalbooleanisDaemon(){//直接返回当前对象的守护线程属性returndaemon;    }

线程状态

先来个线程状态图:

获取线程状态:

publicStategetState(){//由虚拟机实现,获取当前线程的状态returnsun.misc.VM.toThreadState(threadStatus);    }复制代码

线程状态主要由内部枚举类State组成:

publicenumState {              NEW,              RUNNABLE,              BLOCKED,              WAITING,              TIMED_WAITING,              TERMINATED;    }

NEW:刚刚创建,尚未启动的线程处于此状态

RUNNABLE:在Java虚拟机中执行的线程处于此状态

BLOCKED:被阻塞等待监视器锁的线程处于此状态,比如线程在执行过程中遇到synchronized同步块,就会进入此状态,此时线程暂停执行,直到获得请求的锁

WAITING:无限期等待另一个线程执行特定操作的线程处于此状态

通过 wait() 方法等待的线程在等待 notify() 方法

通过 join() 方法等待的线程则会等待目标线程的终止

TIMED_WAITING:正在等待另一个线程执行动作,直到指定等待时间的线程处于此状态

通过 wait() 方法,携带超时时间,等待的线程在等待 notify() 方法

通过 join() 方法,携带超时时间,等待的线程则会等待目标线程的终止

TERMINATED:已退出的线程处于此状态,此时线程无法再回到 RUNNABLE 状态

线程休眠

这是一个静态的本地方法,使当前执行的线程休眠暂停执行millis毫秒,当休眠被中断时会抛出InterruptedException中断异常

/**    * 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.    *    *@parammillis    *        the length of time to sleep in milliseconds    *    *@throwsIllegalArgumentException    *          if the value of {@codemillis} is negative    *    *@throwsInterruptedException    *          if any thread has interrupted the current thread. The    *          interrupted status of the current thread is    *          cleared when this exception is thrown.    */publicstaticnativevoidsleep(longmillis)throwsInterruptedException;

检查线程是否存活

本地方法,测试此线程是否存活。 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态。

/**    * Tests if this thread is alive. A thread is alive if it has    * been started and has not yet died.    *    *@returntrue if this thread is alive;    *          false otherwise.    */publicfinalnativebooleanisAlive();

线程优先级

设置线程优先级

/**    * Changes the priority of this thread.    *

    * First the checkAccess method of this thread is called    * with no arguments. This may result in throwing a    * SecurityException.    *

    * Otherwise, the priority of this thread is set to the smaller of    * the specified newPriority and the maximum permitted    * priority of the thread's thread group.    *    *@paramnewPriority priority to set this thread to    *@exceptionIllegalArgumentException  If the priority is not in the    *              range MIN_PRIORITY to    *              MAX_PRIORITY.    *@exceptionSecurityException  if the current thread cannot modify    *              this thread.    *@see#getPriority    *@see#checkAccess()    *@see#getThreadGroup()    *@see#MAX_PRIORITY    *@see#MIN_PRIORITY    *@seeThreadGroup#getMaxPriority()    */publicfinalvoidsetPriority(intnewPriority){//线程组ThreadGroup g;//检查安全权限checkAccess();//检查优先级形参范围if(newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {thrownewIllegalArgumentException();        }if((g = getThreadGroup()) !=null) {//如果优先级形参大于线程组最大线程最大优先级if(newPriority > g.getMaxPriority()) {//则使用线程组的优先级数据newPriority = g.getMaxPriority();            }//调用本地设置线程优先级方法setPriority0(priority = newPriority);        }    }

线程中断

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

这里关于线程中断的方法涉及三个:

//实例方法,通知线程中断,设置标志位publicvoidinterrupt(){}//静态方法,检查当前线程的中断状态,同时会清除当前线程的中断标志位状态publicstaticbooleaninterrupted(){}//实例方法,检查当前线程是否被中断,其实是检查中断标志位publicbooleanisInterrupted(){}

interrupt() 方法解析

/**    * Interrupts this thread.    *    *

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

If this thread is blocked in an invocation of the {@link* Object#wait() wait()}, {@linkObject#wait(long) wait(long)}, or {@link* Object#wait(long, int) wait(long, int)} methods of the {@linkObject}    * class, or of the {@link#join()}, {@link#join(long)}, {@link* #join(long, int)}, {@link#sleep(long)}, or {@link#sleep(long, int)},    * methods of this class, then its interrupt status will be cleared and it    * will receive an {@linkInterruptedException}.    *    *

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

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

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

    *    *

Interrupting a thread that is not alive need not have any effect.    *    *@throwsSecurityException    *          if the current thread cannot modify this thread    *    *@revised6.0    *@specJSR-51    */publicvoidinterrupt(){//检查是否是自身调用if(this!= Thread.currentThread())//检查安全权限,这可能导致抛出{@link * SecurityException}。checkAccess();//同步代码块synchronized(blockerLock) {            Interruptible b = blocker;//检查是否是阻塞线程调用if(b !=null) {//设置线程中断标志位interrupt0();//此时抛出异常,将中断标志位设置为false,此时我们正常会捕获该异常,重新设置中断标志位b.interrupt(this);return;            }        }//如无意外,则正常设置中断标志位interrupt0();    }

线程中断方法不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦~

只能由自身调用,否则可能会抛出SecurityException

调用中断方法是由目标线程自己决定是否中断,而如果同时调用了wait,join,sleep等方法,会使当前线程进入阻塞状态,此时有可能发生InterruptedException异常

被阻塞的线程再调用中断方法是不合理的

中断不活动的线程不会产生任何影响

检查线程是否被中断:

/**    * Tests whether this thread has been interrupted.  The interrupted    * status of the thread is unaffected by this method.          测试此线程是否已被中断。, 线程的中断*状态不受此方法的影响。    *    *

A thread interruption ignored because a thread was not alive    * at the time of the interrupt will be reflected by this method    * returning false.    *    *@returntrue if this thread has been interrupted;    *          false otherwise.    *@see#interrupted()    *@revised6.0    */publicbooleanisInterrupted(){returnisInterrupted(false);    }

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

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

A thread interruption ignored because a thread was not alive    * at the time of the interrupt will be reflected by this method    * returning false.    *    *@returntrue if the current thread has been interrupted;    *          false otherwise.    *@see#isInterrupted()    *@revised6.0    */publicstaticbooleaninterrupted(){returncurrentThread().isInterrupted(true);    }

总结

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

在此我向大家推荐一个架构学习交流群。交流学习群号:938837867 暗号:555 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备

相关文章

网友评论

    本文标题:从源码的角度再学「Thread」

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