前言
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性能优化、分布式架构等这些成为架构师必备
网友评论