应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

腾讯架构师理解的并发编程基石Thread类的工作原理

  1。开篇词
  说到并发编程,可能大家脑海中的第一印象会是Thread、多线程、JUC、线程池、ThreadLocal等等内容。确实,并发编程是Java编程中不可或缺的一部分,掌握并发编程的核心技术,在求职面试中会是摧城拔寨的利器。而今天将要跟大家一起聊聊的是:并发编程的基石Thread类的工作原理。
  事实上,在笔者回忆关于Thread类的核心API以及对应的线程状态转换关系时,总觉得印象有一些模糊,故此才有这篇文章。本文的核心议题是Thread类,由此延伸出诸多议题,例如:进程与线程、线程状态及生命周期、ThreadAPI的用法等等。2。进程与线程
  首先,有必要介绍一下进程与线程,以及它们之间的区别与关系。
  进程是操作系统分配资源的基本单位,比如我们在启动一个main此时就启动了一个JVM进程。
  而线程则是比进程纬度更小的单位,它是CPU分配的基本单位(因为真正占用运行的就是线程),比如启动一个main方法后它所在的线程就属于这个JVM进程的一个线程,它的名字叫主线程。一个进程可以有一个或多个线程,同一个进程中的各个线程之间共享进程的内存空间。
  进程与线程之间的区别如下:进程是操作系统分配资源的最小单位,而线程是CPU分配(程序执行)的最小单位一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间调度和切换:线程上下文切换比进程上下文切换要快得多3。Thread常用API
  鉴于笔者此前对Thread类核心API知之甚少,对其原理不甚了解,于是本小节主要内容就是介绍Thread类核心API的用法、意义及对线程状态的影响等内容。3。1创建线程任务
  在调用Thread类的API之前,需要先创建线程对象,这很简单,只需要newThread()就可以了。但是事实上,如果只通过new创建线程并未做其他任何操作,那么这个线程将不会执行任何业务逻辑。
  所以我们需要通过另外的手段为线程指定其执行的业务逻辑,那么问题来了:创建线程任务有几种形式?
  一般来说,我们认为有三种形式:继承Thread类、实现Runnable接口以及实现Callable接口,下面一一进行详述。3。1。1继承Thread类
  创建一个类,让它继承Thread类并覆盖run方法,run方法中指定了线程执行的业务逻辑。这样在以后创建线程时可以直接实例化该类即可,在启动线程后程序会自动去执行覆盖的run方法逻辑。publicclassCreateThreadByThreadextendsThread{Overridepublicvoidrun(){System。out。println(CreateThreadByRunnablerun,自定义的业务逻辑);}publicstaticvoidmain(String〔〕args){ThreadthreadnewCreateThreadByThread();thread。start();}}输出CreateThreadByRunnablerun,自定义的业务逻辑3。1。2实现Runnable接口
  创建一个类,实现Runnable接口并覆盖run方法是,然后再去创建一个Thread类,并将实现Runnable接口的对象作为入口传入Thread类的构造器,在启动Thread对象后程序会去执行Runnable对象的run方法。publicclassCreateThreadByRunnableimplementsRunnable{Overridepublicvoidrun(){System。out。println(CreateThreadByRunnablerun,自定义的业务逻辑);}publicstaticvoidmain(String〔〕args){RunnablerunnablenewCreateThreadByRunnable();将runnable对象作为入参传入Thread类构造器ThreadthreadnewThread(runnable);thread。start();}}
  虽然以上两种形式一个是继承Thread类,一个是实现Runnable接口,但如果细看源码的话并没有本质上的区别。
  首先看继承Thread类似的形式,它需要覆盖run方法,我们来看看Threadrun默认内容是什么:ThreadrunOverridepublicvoidrun(){if(target!null){target。run();}}ThreadtargetWhatwillberun。privateRunnabletarget;
  事实上,Threadrun方法是覆盖了Runnable接口的run方法,它的逻辑就是当Runnable类型私有成员变量不为空时,执行其run方法。
  而实现Runnable接口的形式,我们在创建完Runnable类型的对象后,需要将它作为入参传入Thread类的构造器。ThreadThread(java。lang。Runnable)publicThread(Runnabletarget){init(null,target,ThreadnextThreadNum(),0);}privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,longstackSize,AccessControlContextacc,booleaninheritThreadLocals){省略其他代码this。targettarget;省略其他代码}
  可以看到在Thread类似的重载构造方法中,传入的Runnable类型的对象赋值给了Thread类的target私有成员变量。再联系我们刚刚提到的Threadrun方法:当Runnable类型私有成员变量不为空时,执行其run方法。
  这不就是换了个皮吗?
  所以实现Runnable接口的形式跟继承Thread类的形式并没有本质上的区别,它们都是基于覆盖run方法来实现改变线程需要执行的任务。3。1。3实现Callable接口
  Callable接口是JDK1。5才引入的类,它的功能比Runnable更强大,最大的特点是Callable允许有返回值,其次它支持泛型,同时它允许抛出异常被外层代码捕获,下面是实现Callable接口来创建线程的示例:publicclassCreateThreadByCallableimplementsCallableInteger{publicstaticvoidmain(String〔〕args){CreateThreadByCallablecallablenewCreateThreadByCallable();FutureTaskIntegerfuturenewFutureTask(callable);创建线程并启动ThreadthreadnewThread(future);thread。start();Integerintegernull;try{integerfuture。get();}catch(InterruptedExceptionExecutionExceptione){e。printStackTrace();}System。out。println(FutureTask返回内容:integer);}OverridepublicIntegercall()throwsException{System。out。println(CreateThreadByCallablecall,自定义的业务逻辑,返回1);return1;}}
  值得注意的是,我们需要基于FutureTask类配合使用Callale接口,返回值、异常以及泛型都是FutureTask类提供的特性。
  当深入FutureTask的构造器以及其内部方法时,笔者发现了一些新东西。FutureTaskFutureTask(java。util。concurrent。CallableV)publicFutureTask(CallableVcallable){if(callablenull)thrownewNullPointerException();this。callablecallable;this。stateNEW;}
  首先在FutureTask的构造器中,将Callable对象赋值给了FutureTask类的Callable类型私有成员变量。然后继续构造Thread对象,笔者发现咱们使用的Thread重载构造方法竟然与实现Runnable接口的场景是一致的,也就是说FutureTask实现了Runnable接口,打开源码一看,果然如此。FutureTask类实现了RunnableFuture接口publicclassFutureTaskVimplementsRunnableFutureV{}RunnableFuture接口继承于Runnable接口publicinterfaceRunnableFutureVextendsRunnable,FutureV{}
  所以我们构造的FutureTask对象是作为Runnable类型传入Thread类中的,在线程启动时会去执行FutureTask内部的run方法,我们再来看看FutureTaskrun方法。java。util。concurrent。FutureTaskrunpublicvoidrun(){if(state!NEW!UNSAFE。compareAndSwapObject(this,runnerOffset,null,Thread。currentThread()))return;try{CallableVccallable;if(c!nullstateNEW){Vresult;booleanran;try{执行callable属性的call方法获取返回值resultc。call();rantrue;}catch(Throwableex){resultnull;ranfalse;setException(ex);}if(ran)若执行完毕,将返回值赋值给outcome属性(在FutureTaskget方法中返回)set(result);}}finally{runnernull;intsstate;if(sINTERRUPTING)handlePossibleCancellationInterrupt(s);}}java。util。concurrent。FutureTasksetprotectedvoidset(Vv){if(UNSAFE。compareAndSwapInt(this,stateOffset,NEW,COMPLETING)){outcomev;UNSAFE。putOrderedInt(this,stateOffset,NORMAL);finalstatefinishCompletion();}}
  可以看到,在FutureTaskrun方法中实际上执行的是Callablecall方法。所以说,实现Callable接口的形式,最终Thread执行的内容还是Threadrun方法,只不过这个run方法是被FutureTask类的run方法覆盖了,而调用Callablecall方法是FutureTaskrun的内部预定义逻辑。3。1。4创建线程的方法
  通过以上三种创建线程任务的形式以及对它们源码的探究,我们可以知道,无论是哪种形式最终还是以覆盖Threadrun的形式来实现的。
  这里有一个题外话:创建线程的方法有几种?
  这个问题在网络上经常被误解读,大部分观点都认为可以通过继承Thread类、实现Runnable接口以及实现Callable接口来创建线程。事实上我在上文中描述的是创建线程任务的形式,想要突出的并非创建线程的方法而是创建线程执行任务的方法。
  在上文的示例代码中我们可以知道,即便是通过后两种形式(即实现Runnable接口、实现Callable接口),我们最终还是需要newThread来创建一个线程,只不过是通过传入Runnable对象来改变了线程的行为。
  所以说,创建线程的方法只有一种:newThread()。3。2start
  Threadstart可以说是Thread类最常用的方法了,这个方法的作用是让线程开始执行。
  调用刚创建好的线程的start方法后,该线程将会从NEW状态转化成RUNNABLE状态,CPU会在合适的时间分配给该线程时间片,真正执行线程的业务方法。
  需要注意的是,一个线程只能调用一次start方法,否则就会抛出IllegalThreadStateException异常,原因是在Threadstart方法中,首先会判断线程状态。java。lang。Threadstartpublicsynchronizedvoidstart(){若线程状态不为NEW,抛出异常AzerostatusvaluecorrespondstostateNEW。(0值对应的状态是NEW)if(threadStatus!0)thrownewIllegalThreadStateException();省略其他逻辑。。。}
  可以看到,当线程的状态不是NEW状态时,再次调用它的start方法,将会抛出IllegalThreadStateException异常。也就是说,线程一旦完成执行,就不能重新启动了。3。3join
  Threadjoin方法的作用是等待线程执行完毕,在JDK1。8中该方法有三个重载方法,另外两个带参数的重载方法是设置了超时时间:
  Threadjoin方法的意义可以这样描述:在A线程内调用B线程的join方法,A线程会等待B线程执行完毕再执行。在这个过程中,线程A的状态将由RUNNABLE转变为WAITING,B线程执行完毕后,A线程状态将转变为RUNNABLE,该结论可以通过下面的示例来验证:publicstaticvoidmain(String〔〕args)throwsInterruptedException{Threadthread1newThread((){System。out。println(thread1isrunning);try{为观察效果明显,将睡眠时间设置的长一点Thread。sleep(50000);}catch(InterruptedExceptione){e。printStackTrace();}System。out。println(thread1isover);});thread1。start();thread1。join();}
  运行该程序,首先将输出thread1isrunning,然后thread1线程进入sleep方法,sleep结束后输出thread1isover。而在thread1线程sleep的过程中,打开jconsole工具,可以观察到调用了thread1。join方法的main线程状态是WAITING。
  上面说的是Threadjoin方法,如果是设置了超时时间的重载方法,调用某个线程对象join方法的线程状态将转变为TIMEDWAITING。
  除此之外,还有一个问题值得思考:在当前线程调用其他线程的join方法后,若其他线程尝试获取当前线程已持有的锁,是否会成功?我们来做个实验。privatestaticStringstr123;privatestaticvoidtestJoinLock()throwsInterruptedException{main线程先占用str资源synchronized(str){Threadthread1newThread((){System。out。println(thread1isrunning);子线程尝试占用str资源synchronized(str){System。out。println(thread1isgetstrlock);}System。out。println(thread1isover);});thread1。start();thread1。join();}}
  先声明一个共享资源str变量,main线程首先对该变量加上同步锁,然后实例化一个子线程,子线程中也尝试去给str加上同步锁。运行该程序,观察到最终输出的内容是:thread1isrunning,且程序一直未终止。
  猜测可能子线程时被阻塞了,打开jconsole,如下图:
  果然,观察到Thread0线程的状态是BLOCKED,且资源拥有者是main,即该线程被main线程阻塞了。
  所以,当线程A因调用线程B线程的join方法而进入WAITING状态时,并不会释放本身已持有的锁资源。3。4yield
  Threadyield方法的作用是释放时间片,让CPU再次选择线程执行。这句话潜在的意思是说CPU可能选中之前放弃时间片的线程来执行。
  值得注意的是,Threadyield方法不会释放已经持有的锁资源。3。5interrupt
  Threadinterrupt方法的作用是中断线程,调用线程的该方法会请求终止当前线程,需要注意的是该方法仅仅是给当前线程发送了一个终止的信息,并设置中断标志位,最终是否终止是线程自己处理的。
  还有两个比较类似的方法:Threadinterrupted,ThreadisInterrupted。
  Threadinterrupt方法的作用是检查该线程是否被中断,同时清除中断标志位。
  ThreadisInterrupted方法的作用是检查该线程是否被中断,但不清除中断标志位。
  值得注意的是,当调用线程的Threadinterrupt方法时,若当前线程处于TIMEDWAITING或WAITING状态时(如调用过Objectwait,Threadjoin,Threadsleep或对应重载方法的线程),将会抛出InterruptedException异常,使得线程直接进入TERMINATED状态。publicstaticvoidmain(String〔〕args){Threadthread1newThread((){System。out。println(thread1isrunning);try{为观察效果明显,将睡眠时间设置的长一点Thread。sleep(50000);}catch(InterruptedExceptione){e。printStackTrace();}System。out。println(thread1isover);});thread1。start();中断线程thread1。interrupt();}
  执行该方法,输出内容为:thread1isrunningthread1isoverjava。lang。InterruptedException:sleepinterruptedatjava。lang。Thread。sleep(NativeMethod)atio。walkers。planes。pandora。jdk。thread。usage。InterruptMethod。lambdamain0(InterruptMethod。java:16)atjava。lang。Thread。run(Thread。java:748)
  可以看到,程序抛出了InterruptedException异常。4。线程状态
  在Java语言中,线程被抽象成Thread类,而在Thread类中有一个State枚举类,它描述了线程的各种状态。java。lang。Thread。StatepublicenumState{NEW,RUNNABLE,BLOCKED,WAITING,TIMEDWAITING,TERMINATED;}
  JDK源码的注释对于线程状态的描述如下:NEW:未启动的线程处于这种状态RUNNABLE:在JVM中执行的线程处于这种状态BLOCKED:等待锁而被阻塞的线程处于这种状态WAITING:一个线程正在无限期地等待另一个线程执行某个特定的操作,它就处于这种状态TIMEDWAITING:在指定的等待时间内等待另一个线程执行某个操作的线程处于这种状态TERMINATED:已退出的线程处于这种状态
  下面我就用代码模拟处于各个状态的线程,同时会例举线程进入该状态的方法。4。1NEW
  未启动的线程处于NEW状态,这个状态十分容易模拟,当我new出一个Thread对象后,该Thread线程就处于NEW状态,模拟代码如下:publicstaticvoidmain(String〔〕args){ThreadthreadnewThread();System。out。println(Threadstateis:thread。getState());}程序输出内容Threadstateis:NEW
  所以创建一个线程对象后,该线程的状态就是NEW状态。4。2RUNNABLE
  在JVM中执行的线程处于RUNNABLE状态,即当调用一个线程的start方法后,等待CPU分配给该线程时间片,该线程正式执行时,他就处于RUNNABLE状态,模拟代码如下:publicstaticvoidmain(String〔〕args){ThreadthreadnewThread(()System。out。println(Threadstateis:Thread。currentThread()。getState()));thread。start();}程序输出内容Threadstateis:RUNNABLE
  所以调用一个线程的start方法后,若未抛出异常,该线程就会进入RUNNABLE状态。
  这里需要注意的是,若对于非NEW状态的线程调用它的start方法,将会抛出IllegalThreadStateException异常,原因源码如下:java。lang。Threadstartpublicsynchronizedvoidstart(){若线程状态不为NEW,抛出异常AzerostatusvaluecorrespondstostateNEW。(0值对应的状态是NEW)if(threadStatus!0)thrownewIllegalThreadStateException();省略其他逻辑。。。}
  除此之外,还有几种情况也会进入RUNNABLE状态:BLOCKED状态下的线程因获取锁成功而进入RUNNABLE状态因调用sleep,join方法而进入WAITINGTIMEDWAITING状态的线程,超过超时时间、正常等待结束或调用Objectnotify,ObjectnotifyAll方法,会进入RUNNABLE状态RUNNABLE状态的线程因调用yield方法而重新进入RUNNABLE状态4。3BLOCKED
  线程由于等待锁而被阻塞将处于BLOCKED状态,想要模拟该状态下的线程就需要引入共享资源以及第二个线程了,线程1先启动并占有锁资源,然后再启动线程2,当线程2尝试获取锁资源时,发现共享资源已被线程1占用,于是进入阻塞状态。模拟代码如下:publicclassStateBlocked{共享资源privatestaticStringstrlock;publicstaticvoidmain(String〔〕args)throwsInterruptedException{Threadthread1newThread((){synchronized(str){System。out。println(Thread1getlock);防止线程thread1释放str锁资源try{Thread。sleep(10000);}catch(InterruptedExceptione){e。printStackTrace();}}});thread1。start();保证thread1先拿到锁资源Thread。sleep(1000);Threadthread2newThread((){synchronized(str){System。out。println(Thread1getlock);}});thread2。start();保证thread2进入synchronized代码块Thread。sleep(1000);System。out。println(Thread2stateis:thread2。getState());}}
  上述模拟代码的输出结果如下:Thread1getlockThread2stateis:BLOCKEDThread1getlock
  所以线程由于进入同步块尝试获取锁失败被阻塞时,其状态就是BLOCKED状态。4。4WAITING
  一个线程正在无限期地等待另一个线程执行某个特定的操作,它就处于WAITING状态。启动一个线程A,在另一个线程B中调用Threadjoin方法,线程B会等待线程A执行完毕,这时线程B就是WAITING状态,模拟代码如下:publicstaticvoidmain(String〔〕args)throwsInterruptedException{锁资源Stringstrlock;ThreadthreadnewThread((){sleep100s是为了有足够的时间查看线程状态try{Thread。sleep(100000);}catch(InterruptedExceptione){e。printStackTrace();}});thread。start();主线程等待thread线程执行完毕thread。join();}
  执行该方法后,我们需要打开jconsole,找到对应进程,查看其线程状态,如下图:
  可以看到,此时main线程的状态是WAITING。
  所以,调用一个线程的join方法(不指定超时时间),调用方将进入WAITING状态。
  除此之外,调用一个线程的Objectwait(不指定超时时间),调用方也将进入WAITING状态。4。5TIMEDWAITING
  在指定的等待时间内等待另一个线程执行某个操作的线程处于TIMEDWAITING状态。TIMEDWAITING状态与WAITING状态唯一的不同就是前者指定了超时时间,在上一步代码基础上略作改动,我们就可以模拟出TIMEDWAITING状态,模拟代码如下:publicstaticvoidmain(String〔〕args)throwsInterruptedException{锁资源Stringstrlock;ThreadthreadnewThread((){sleep100s是为了有足够的时间查看线程状态try{Thread。sleep(100000);}catch(InterruptedExceptione){e。printStackTrace();}});thread。start();主线程等待thread线程执行完毕,指定超时时间为10sthread。join(10000);}
  执行该方法后,打开jconsole,找到对应进程,查看其线程状态,如下图:
  可以看到,此时main线程的状态是TIMEDWAITING。
  所以,调用一个线程的join方法(指定超时时间),调用方将进入TIMEDWAITING状态
  除此之外,调用一个线程的Objectwait(指定超时时间),调用方也将进入WAITING状态。4。5TERMINATED
  已退出的线程处于TERMINATED状态,这个状态就是线程自然结束的状态,十分容易模拟,代码如下:publicstaticvoidmain(String〔〕args)throwsInterruptedException{ThreadthreadnewThread();thread。start();等待线程thread执行完毕thread。join();System。out。println(Threadstateis:thread。getState());}
  所以,当一个线程正常结束时,它将进入TERMINATED状态。
  除此之外,当一个线程抛异常退出时,也会进入TERMINATED状态,例如在WAITINGTIMEDWAITING状态下的线程调用Threadinterrupt方法而退出。5。线程状态转换图
  将上述的线程状态转换关系总结为如下图:
  6。小结
  本文首先介绍了进程与线程的联系与区别,随后描述了Thread类常用的API以及创建线程执行任务的形式,然后详细说明了线程的状态并作出示例,最后总结了线程状态转换图。
  总结了几个小问题:进程与线程的联系与区别创建线程任务的形式有哪几种?创建线程有几种方式?线程状态有哪几种?描述一下线程状态的转换规则

6个自由球员,湖人阵容又要大换血,三人离队无悬念,一人或续约湖人队在无缘季后赛之后即将开启休赛期的操作。现在很多人都在关注威少的去向,以现在来看湖人队想要去交易威少难度还是非常大的,毕竟威少下赛季可以自己决定去留。除了威少之外湖人队今年……谷爱凌美国豪宅照流出,三层独栋还有大院,市值2000万左右说到谷爱凌,大家再熟悉不过,北京冬奥会的出色表现,让大家对这位青蛙公主,更加的喜爱和赞叹。谷爱凌在赛场上,是潇洒自信的,那么在现实生活中是什么样的,谷爱凌表示,自己和姥姥……吃了这些让你远离心血管疾病饮食中卡路里和脂肪的数量是正确的,是照顾心脏的重要部分,有些食物在这方面特别有益,因为它们的营养成分。美国心脏协会建议饮食富含水果和蔬菜、全谷物、低脂乳制品、家禽、鱼和坚果。这……胡金秋有望获得常规赛MVP,北控排名第十三再次无缘季后赛CBA常规赛已经过去了三十七轮,还有一轮比赛本赛季常规赛就宣告结束,在常规赛除了球迷关注的12强能够进入季后赛的队伍之外,球迷们自然也非常关注MVP最终会花落谁家,前两个赛季由……太惨!NBA科比修复图公布,四肢没了三,瓦妮莎花费百万为其修NBA巨星科比拥有一个近乎完美的职业生涯,他的荣誉数不胜数,而这一切也都是他用辛勤的努力和高度的自律换来的。而科比给NBA后来人留下了宝贵的曼巴精神,他永不言败的精神也是激励了……太阳系尘埃来自小行星,科学家研究小组出人意料地推翻了最新研究表明,太阳系尘埃可能来自火星系统,而非通常认为的小行星带。日出和日落时所看到的光亮,实际上是宇宙尘埃造成的结果。天文学家们数十年来一直认为这些尘埃来自小行星,但是……新生活我们家的露营生活文星星我想买彩票赢得40000卢布,这样就可以和讨厌的写作一刀两断,再买点土地,和伊凡相邻而居,我期望着能给你们这两个穷亲戚一人5亩地。总的来说,我的生活很乏味,已经厌倦……35岁危机来临,什么行业才是下一个机遇?互联网圈子里一直有一个说法,叫35岁危机,这几乎是悬在每一个互联网人头上的达摩克利斯之剑。不管是许多用人单位在招聘时明文规定的35岁以下,还是企业高龄员工经常成为裁员首当其冲的……恐龙并非因小行星而灭绝?科学家全球冰河世纪,导致恐龙灭绝科学家:小行星撞击地球是导致恐龙最终灭绝的原因小行星撞击地球后,各种各样的自然灾害接踵而至,这个地球的气候环境以及生态系统,彻底陷入了崩坏状态,在这样的情况下,体积巨大吃……普京免费提供30万吨16日,俄罗斯总统普京在出席上合组织峰会时,敦促解决俄罗斯化肥出口在货运和保险等方面遇到的问题。他表示,俄罗斯准备将该国滞压在欧洲港口的30万吨化肥免费提供给急需化肥的发展中国……京东发布2022年企业线上福利采购趋势洞察报告企业福利定制化一年一度的中秋发福季即将来临,每年的这个时候,发什么?怎么发?成为企业最关注的话题,各企业别出心裁、费尽心思,希望能够给每位员工送去令人满意的中秋福利,提升员工的幸福感。在中秋……DoctorCurious41天体物理理解的进步中国科学院理导言:纵观三千年来天文学的思想和发现,我们就知道单凭高明的猜想是不够的。实质进步主要来自于引入新的观测工具和理论工具。作者简介Profile作者:马丁哈维特(……
苹果iPhone15拍照要让安卓机颤抖?高成本潜望镜安排上了安卓手机的拍照都安排上两亿像素了,而苹果却似乎不为所动没有参与到与安卓机的竞赛上来,一直延续了平时的配置。就在最近,有消息爆料了iPhone15采用高单价潜望镜设计,会让拍照提……又现逃逸式离职?年薪700万的金融高管辞职三年后被查7月26日下午,据中央纪委国家监委驻光大集团纪检监察组、安徽省纪委监委消息,中国光大控股有限公司原党委书记、行政总裁陈爽涉嫌严重违法,目前正接受监察调查。今年55岁的陈爽……加拿大石窟桑拿房Partisans这是一个位于加拿大湛蓝的休伦湖畔临水而建的桑拿浴室,由Partisans工作室设计,内部空间由优美精致的曲面木构打造,犹如置身一个木头的洞穴之中。这里的位置如此得天独厚,……一种新的激烈宇宙事件在恒星表面相对集中的一处,在短短几小时里,就能燃烧掉质量相当于35亿个吉萨大金字塔的物质。这就是天文学家新发现的一类难以置信的激烈宇宙事件。在TESS(凌星系外行星巡天卫……拍一部戏就退圈的演员有人入行三年倒欠百万,有人下海成女霸总娱乐圈里有很多演员很高产,一年能拍好多部戏,比如这几年来霸屏的刘涛,单是2021年就上线了五部作为女主角的大戏,还有其他客串以及综艺节目没算在内。而与之相反的是,有的人入……叛逆期孩子的五大表现(叛逆期孩子的心理成因及对策)叛逆儿童的五种表现(叛逆儿童的心理原因及对策)一、叛逆的孩子确实有很多表现:叛逆的孩子不喜欢被别人控制的感觉。他们根本不听大人的教诲。有时候他们无能为力。他们……人类处于银河系当中,我们是如何知道银河系的形状的?你有没有想过,人类目前连太阳系还没有飞出去过,是怎么知道银河系是什么形状的呢?今天就来说一说,人类是如何识得银河系的庐山真面目!他叫威廉赫歇尔,是英国著名的天文学家。在1……论道德绑架之所以以此为题,是因为图1这篇文章,别有用心。之所以说是别有用心,原因有两点:第一,此人所说的那届国青女排只招9899年龄段,并不招收00后,所以压根没有征召李盈莹……科普!带您了解加拿大不同类型工作签证!到底什么是LMIA豁免加拿大工签外籍工人在加拿大工作需要合法的工作签证,加拿大工作签证的类别一般分为封闭工签与开放工签。加拿大移民局每年会为外籍工人发出数十万份工作签证,以此来促进加拿大本土经……地球是沙子,人类是细菌?最远探测器拍摄的地球照片,引人深思来自深空的一次拍摄世界上第一张照片拍摄于1826年,这是已知最早的一张照片。自此以后,人类进入了图像记录的时代,数百年间,图像技术不断升级。到了今天已经能够进行几乎无损的……官宣!面部4处骨折!CBA最炸新秀报销了一名后卫在高速突破行进间,与身高2米12、体重135公斤大中锋的肘部相撞,结果会是怎样?据媒体人周赫透露颧骨出现两处骨折,从而导致赛季报销。北控方面的官宣,则是4处骨折,……微软宣布推出SurfacePro9Liberty特别版拥有激微软在与伦敦的设计公司Liberty达成合作后发布了SurfacePro9Liberty特别版。特别版Pro9的宣布是为了庆祝微软标志性的二合一设备诞生10周年。Surface……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网