大家好,今天小编来为大家解答以下的问题,关于企业网站开源源码分享,企业网站整站源码这个很多人还不知道,现在让我们一起来看看吧!
本文分享自华为云社区《【高并发】Thread类的源码精髓-云社区-华为云》,作者:冰河。
前言
最近和一个朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread创建线程,那你看过Thread类的源码吗?我这个朋友自然是没看过Thread类的源码,然后,就没有然后了!!!
所以,我们学习技术不仅需要知其然,更需要知其所以然,今天,我们就一起来简单看看Thread类的源码。
注意:本文是基于JDK1.8来进行分析的。
Thread类的继承关系
我们可以使用下图来表示Thread类的继承关系。
由上图我们可以看出,Thread类实现了Runnable接口,而Runnable在JDK1.8中被@FunctionalInterface注解标记为函数式接口,Runnable接口在JDK1.8中的源代码如下所示。
@FunctionalInterface\npublicinterfaceRunnable{\npublicabstractvoidrun();\n}\n
Runnable接口的源码比较简单,只是提供了一个run()方法,这里就不再赘述了。
接下来,我们再来看看@FunctionalInterface注解的源码,如下所示。
@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic@interfaceFunctionalInterface{}\n
可以看到,@FunctionalInterface注解声明标记在Java类上,并在程序运行时生效。
Thread类的源码剖析
Thread类定义
Thread在java.lang包下,Thread类的定义如下所示。
publicclassThreadimplementsRunnable{\n
加载本地资源
打开Thread类后,首先,我们会看到在Thread类的最开始部分,定义了一个静态本地方法registerNatives(),这个方法主要用来注册一些本地系统的资源。并在静态代码块中调用这个本地方法,如下所示。
//定义registerNatives()本地方法注册系统资源\nprivatestaticnativevoidregisterNatives();\nstatic{\n//在静态代码块中调用注册本地系统资源的方法\nregisterNatives();\n}\n
Thread中的成员变量
Thread类中的成员变量如下所示。
//当前线程的名称\nprivatevolatileStringname;\n//线程的优先级\nprivateintpriority;\nprivateThreadthreadQ;\nprivatelongeetop;\n//当前线程是否是单步线程\nprivatebooleansingle_step;\n//当前线程是否在后台运行\nprivatebooleandaemon=false;\n//Java虚拟机的状态\nprivatebooleanstillborn=false;\n//真正在线程中执行的任务\nprivateRunnabletarget;\n//当前线程所在的线程组\nprivateThreadGroupgroup;\n//当前线程的类加载器\nprivateClassLoadercontextClassLoader;\n//访问控制上下文\nprivateAccessControlContextinheritedAccessControlContext;\n//为匿名线程生成名称的编号\nprivatestaticintthreadInitNumber;\n//与此线程相关的ThreadLocal,这个Map维护的是ThreadLocal类\nThreadLocal.ThreadLocalMapthreadLocals=null;\n//与此线程相关的ThreadLocal\nThreadLocal.ThreadLocalMapinheritableThreadLocals=null;\n//当前线程请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理\nprivatelongstackSize;\n//线程终止后存在的JVM私有状态\nprivatelongnativeParkEventPointer;\n//线程的id\nprivatelongtid;\n//用于生成线程id\nprivatestaticlongthreadSeqNumber;\n//当前线程的状态,初始化为0,代表当前线程还未启动\nprivatevolatileintthreadStatus=0;\n//由(私有)java.util.concurrent.locks.LockSupport.setBlocker设置\n//使用java.util.concurrent.locks.LockSupport.getBlocker访问\nvolatileObjectparkBlocker;\n//Interruptible接口中定义了interrupt方法,用来中断指定的线程\nprivatevolatileInterruptibleblocker;\n//当前线程的内部锁\nprivatefinalObjectblockerLock=newObject();\n//线程拥有的最小优先级\npublicfinalstaticintMIN_PRIORITY=1;\n//线程拥有的默认优先级\npublicfinalstaticintNORM_PRIORITY=5;\n//线程拥有的最大优先级\npublicfinalstaticintMAX_PRIORITY=10;\n
从Thread类的成员变量,我们可以看出,Thread类本质上不是一个任务,它是一个实实在在的线程对象,在Thread类中拥有一个Runnable类型的成员变量target,而这个target成员变量就是需要在Thread线程对象中执行的任务。
线程的状态定义
在Thread类的内部,定义了一个枚举State,如下所示。
publicenumState{\n//初始化状态\nNEW,\n//可运行状态,此时的可运行包括运行中的状态和就绪状态\nRUNNABLE,\n//线程阻塞状态\nBLOCKED,\n//等待状态\nWAITING,\n//超时等待状态\nTIMED_WAITING,\n//线程终止状态\nTERMINATED;\n}\n
这个枚举类中的状态就代表了线程生命周期的各状态。我们可以使用下图来表示线程各个状态之间的转化关系。
NEW:初始状态,线程被构建,但是还没有调用start()方法。RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。TIME_WAITING:超时等待状态。可以在一定的时间自行返回。TERMINATED:终止状态,当前线程执行完毕。
Thread类的构造方法
Thread类中的所有构造方法如下所示。
publicThread(){\ninit(null,null,&34;+nextThreadNum(),0);\n}\npublicThread(Runnabletarget){\ninit(null,target,&34;+nextThreadNum(),0);\n}\nThread(Runnabletarget,AccessControlContextacc){\ninit(null,target,&34;+nextThreadNum(),0,acc,false);\n}\npublicThread(ThreadGroupgroup,Runnabletarget){\ninit(group,target,&34;+nextThreadNum(),0);\n}\npublicThread(Stringname){\ninit(null,null,name,0);\n}\npublicThread(ThreadGroupgroup,Stringname){\ninit(group,null,name,0);\n}\npublicThread(Runnabletarget,Stringname){\ninit(null,target,name,0);\n}\npublicThread(ThreadGroupgroup,Runnabletarget,Stringname){\ninit(group,target,name,0);\n}\npublicThread(ThreadGroupgroup,Runnabletarget,Stringname,\nlongstackSize){\ninit(group,target,name,stackSize);\n}\n
其中,我们最经常使用的就是如下几个构造方法了。
publicThread(){\ninit(null,null,&34;+nextThreadNum(),0);\n}\npublicThread(Runnabletarget){\ninit(null,target,&34;+nextThreadNum(),0);\n}\npublicThread(Stringname){\ninit(null,null,name,0);\n}\npublicThread(ThreadGroupgroup,Stringname){\ninit(group,null,name,0);\n}\npublicThread(Runnabletarget,Stringname){\ninit(null,target,name,0);\n}\npublicThread(ThreadGroupgroup,Runnabletarget,Stringname){\ninit(group,target,name,0);\n}\n
通过Thread类的源码,我们可以看出,Thread类在进行初始化的时候,都是调用的init()方法,接下来,我们看看init()方法是个啥。
init()方法
privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,longstackSize){\ninit(g,target,name,stackSize,null,true);\n}\nprivatevoidinit(ThreadGroupg,Runnabletarget,Stringname,\nlongstackSize,AccessControlContextacc,\nbooleaninheritThreadLocals){\n//线程的名称为空,抛出空指针异常\nif(name==null){\nthrownewNullPointerException(&34;);\n}\n\nthis.name=name;\nThreadparent=currentThread();\n//获取系统安全管理器\nSecurityManagersecurity=System.getSecurityManager();\n//线程组为空\nif(g==null){\n//获取的系统安全管理器不为空\nif(security!=null){\n//从系统安全管理器中获取一个线程分组\ng=security.getThreadGroup();\n}\n//线程分组为空,则从父线程获取\nif(g==null){\ng=parent.getThreadGroup();\n}\n}\n//检查线程组的访问权限\ng.checkAccess();\n//检查权限\nif(security!=null){\nif(isCCLOverridden(getClass())){\nsecurity.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);\n}\n}\ng.addUnstarted();\n//当前线程继承父线程的相关属性\nthis.group=g;\nthis.daemon=parent.isDaemon();\nthis.priority=parent.getPriority();\nif(security==null||isCCLOverridden(parent.getClass()))\nthis.contextClassLoader=parent.getContextClassLoader();\nelse\nthis.contextClassLoader=parent.contextClassLoader;\nthis.inheritedAccessControlContext=\nacc!=null?acc:AccessController.getContext();\nthis.target=target;\nsetPriority(priority);\nif(inheritThreadLocals&&parent.inheritableThreadLocals!=null)\nthis.inheritableThreadLocals=\nThreadLocal.createInheritedMap(parent.inheritableThreadLocals);\n/*StashthespecifiedstacksizeincasetheVMcares*/\nthis.stackSize=stackSize;\n\n//设置线程id\ntid=nextThreadID();\n}\n
Thread类中的构造方法是被创建Thread线程的线程调用的,此时,调用Thread的构造方法创建线程的线程就是父线程,在init()方法中,新创建的Thread线程会继承父线程的部分属性。
run()方法
既然Thread类实现了Runnable接口,则Thread类就需要实现Runnable接口的run()方法,如下所示。
@Override\npublicvoidrun(){\nif(target!=null){\ntarget.run();\n}\n}\n
可以看到,Thread类中的run()方法实现非常简单,只是调用了Runnable对象的run()方法。所以,真正的任务是运行在run()方法中的。另外,
需要注意的是:直接调用Runnable接口的run()方法不会创建新线程来执行任务,如果需要创建新线程执行任务,则需要调用Thread类的start()方法。
start()方法
publicsynchronizedvoidstart(){\n//线程不是初始化状态,则直接抛出异常\nif(threadStatus!=0)\nthrownewIllegalThreadStateException();\n//添加当前启动的线程到线程组\ngroup.add(this);\n\t//标记线程是否已经启动\nbooleanstarted=false;\ntry{\n//调用本地方法启动线程\nstart0();\n//将线程是否启动标记为true\nstarted=true;\n}finally{\ntry{\n//线程未启动成功\nif(!started){\n//将线程在线程组里标记为启动失败\ngroup.threadStartFailed(this);\n}\n}catch(Throwableignore){\n/*donothing.Ifstart0threwaThrowablethen\nitwillbepassedupthecallstack*/\n}\n}\n}\n\nprivatenativevoidstart0();\n
从start()方法的源代码,我们可以看出:
start()方法使用synchronized关键字修饰,说明start()方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常。所以,一个线程只能启动一次,多次启动是会抛出异常的。
这里,
也是面试的一个坑:面试官:【问题一】能不能多次调用Thread类的start()方法来启动线程吗?【问题二】多次调用Thread线程的start()方法会发生什么?【问题三】为什么会抛出异常?
调用start()方法后,新创建的线程就会处于就绪状态(如果没有分配到CPU执行),当有空闲的CPU时,这个线程就会被分配CPU来执行,此时线程的状态为运行状态,JVM会调用线程的run()方法执行任务。
sleep()方法
sleep()方法可以使当前线程休眠,其代码如下所示。
//本地方法,真正让线程休眠的方法\npublicstaticnativevoidsleep(longmillis)throwsInterruptedException;\n\npublicstaticvoidsleep(longmillis,intnanos)\nthrowsInterruptedException{\nif(millis<0){\nthrownewIllegalArgumentException(&34;);\n}\n\nif(nanos<0||nanos>999999){\nthrownewIllegalArgumentException(\n&34;);\n}\n\nif(nanos>=500000||(nanos!=0&&millis==0)){\nmillis++;\n}\n\t//调用本地方法\nsleep(millis);\n}\n
sleep()方法会让当前线程休眠一定的时间,这个时间通常是毫秒值,这里需要注意的是:
调用sleep()方法使线程休眠后,线程不会释放相应的锁。
join()方法
join()方法会一直等待线程超时或者终止,代码如下所示。
publicfinalsynchronizedvoidjoin(longmillis)\nthrowsInterruptedException{\nlongbase=System.currentTimeMillis();\nlongnow=0;\n\nif(millis<0){\nthrownewIllegalArgumentException(&34;);\n}\n\nif(millis==0){\nwhile(isAlive()){\nwait(0);\n}\n}else{\nwhile(isAlive()){\nlongdelay=millis-now;\nif(delay<=0){\nbreak;\n}\nwait(delay);\nnow=System.currentTimeMillis()-base;\n}\n}\n}\n\npublicfinalsynchronizedvoidjoin(longmillis,intnanos)\nthrowsInterruptedException{\n\nif(millis<0){\nthrownewIllegalArgumentException(&34;);\n}\n\nif(nanos<0||nanos>999999){\nthrownewIllegalArgumentException(\n&34;);\n}\n\nif(nanos>=500000||(nanos!=0&&millis==0)){\nmillis++;\n}\n\njoin(millis);\n}\n\npublicfinalvoidjoin()throwsInterruptedException{\njoin(0);\n}\n
join()方法的使用场景往往是启动线程执行任务的线程,调用执行线程的join()方法,等待执行线程执行任务,直到超时或者执行线程终止。
interrupt()方法
interrupt()方法是中断当前线程的方法,它通过设置线程的中断标志位来中断当前线程。此时,如果为线程设置了中断标志位,可能会抛出InteruptedExeption异常,同时,会清除当前线程的中断状态。这种方式中断线程比较安全,它能使正在执行的任务执行能够继续执行完毕,而不像stop()方法那样强制线程关闭。代码如下所示。
publicvoidinterrupt(){\nif(this!=Thread.currentThread())\ncheckAccess();\n\nsynchronized(blockerLock){\nInterruptibleb=blocker;\nif(b!=null){\ninterrupt0();//Justtosettheinterruptflag\nb.interrupt(this);\nreturn;\n}\n}\n//调用本地方法中断线程\ninterrupt0();\n}\nprivatenativevoidinterrupt0();\n
总结
作为技术人员,要知其然,更要知其所以然,我那个朋友技术本身不错,各种框架拿来就用,基本没看过常用的框架源码和JDK中常用的API,属于那种CRUD型程序员,这次面试就栽在了一个简单的Thread类上,所以,大家在学会使用的时候,一定要了解下底层的实现才好啊!
点击下方,第一时间了解华为云新鲜技术~
华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云
\u0002
关于本次企业网站开源源码分享和企业网站整站源码的问题分享到这里就结束了,如果解决了您的问题,我们非常高兴。
