1.ãPocoç¬è®°ã线ç¨Thread
2.老生常谈线程基础的源码几个问题
3.直播带货源码,异步处理中会处理两次请求
ãPocoç¬è®°ã线ç¨Thread
PocoçThreadæ¯å¯¹æ ååºstd::threadçå°è£ ï¼åæ¶å®ç±»ä¼¼Javaä¸æ ·ï¼æä¾äºRunnableæ¥å£ãæ以使ç¨ä¸æ¯å¯¹æ Javaçãä¸æ ååºä¸åçæ¯ï¼Poco::Threadå建åè¿è¡æ¶ç¸å离çãè¿ä¸ç¹æ ååºè®¾è®¡ç¡®å®ä¸å¤ªå好ãä¾å¦ä¸é¢ä¾åã
åæ ·çä¾å
ç±ä¸é¢å¯è§ï¼ä½¿ç¨åºæ¬è·Java类似ãå建ä¸è¿è¡ä¹å离äºã
çä¸ä¸ä¸»è¦çè¿è¡æ¥å£ï¼æèªPoco1.9æºç
æºç æ件主è¦å å«
1.Thread.h/Thread.cpp
æä¾å¤é¨è°ç¨æ¥å£
å¨Thread.cppä¸å®ä¹äºä¸¤ç§Holder,源码 RunnableHolderåCallableHolderãHolderææ¯æ¯Pocoæ¡æ¶ä¸ç»å¸¸ç¨å°çï¼æ¯å¯¹æä¸ç§ç±»å对象çæéå è£ ã
Runnable为线ç¨è¿è¡ç±»çåºç±»ï¼
Callable为带ä¸ä¸ªåæ°çæ¹æ³
2.Thread_POSIX.h/Thread_POSIX.cpp
3.Thread_VX.h/Thread_VX.cpp
4.Thread_WIN.h/Thread_WIN.cpp
5.Thread_WINCE.h/Thread_WINCE.cpp
è¿å 个æ件ï¼æ¯ä¸ªæ件ä¸é½å®ä¹äºThreadImplï¼ç¨äºä¸åå¹³å°ä¸çå ·ä½å®ç°ï¼Threadç§æ继æ¿ThreadImpï¼ThreadImpç¨äºåªä¸ä¸ªæ件ç±ç¼è¯å®å³å®ã
顺便说ä¸ä¸POSIXç³»ç»ä¸çå®ç°ãå 为使ç¨çæ¯c++ï¼å½æ¶æ²¡æthreadç±»ï¼æ以ææçå®ç°é½æ¯ä½¿ç¨pthreadåºæ¥å®ç°çãå ·ä½ç使ç¨è¯·åèpthreadææ¯ææ¡£ã
6.ThreadLocal.h/ThreadLocal.cpp
ThreadLocalä¸å®ä¹äºä¸ä¸ªç±»ï¼ TLSAbstractSlotç±»ï¼ TLSSlotç±»ï¼ ThreadLocalStorageç±»
TLSAbstractSlotæ¯åºç±»ï¼TLSSlotæ¯æ¨¡æ¿ç±»ï¼éè¿æ¨¡æ¿ææ¯å 裹äºå ·ä½çç±»åãThreadLocalStorageæ¯ç¨äºçº¿ç¨åå¨ï¼å ·ä½æ¯éè¿ä¸ä¸ªmapæ¥å®ç°ã
å 为1.9使ç¨çæ¯c++ï¼è¿æ²¡æå¼ç¨local_threadå ³é®åï¼æ以è¿éæ¯éè¿è¿ç§æ¹å¼å®ç°ã
ThreadLocalStorageå®ä¹å¦ä¸
é£ä¹Poco::Threadçtlsæ¯å¦ä½å®ä¹çï¼
æºç æ件æ¯è¾å°ï¼ä¸»è¦å¦ä¸æ件
1.Thread.h/Thread.cpp
2.Thread_STD.h/Thread_POSIX.cpp/Thread_VX.cpp/Thread_WIN.cpp
Thread.h 主è¦å¯¹å®ç°ç±»ThreadImpçå è£ ï¼å¹¶å®ä¹äºå¯¹å¤æ¥å£ã
Thread_STD.hå®ä¹äºå é¨å®ç°,主è¦æä¾äºThreadImpç±»
Thread_POSIX.cpp/Thread_VX.cpp/Thread_WIN.cppåå«å®ä¹ä¸åå¹³å°ä¸çå ¼å®¹å®ç°
å¨Thread_STD.hä¸å®ä¹äºå 个éè¦ç±»å
å¨Thread.cppä¸å¢å äºä¸¤ç§
private修饰çThreadDataï¼å®ä¹äºçº¿ç¨å é¨æ°æ®ã 1.9ä¸æºç åå«å®ä¹å¨å个平å°å®ç°ç±»ä¸ï¼è¿éæ½ç¦»åºæ¥å®ä¹å¨Thread.cppä¸ãè¾ä¹åçå®ä¹ï¼è¿éé¢å¤çæ¯æ°å¢äºstd::threadæéãå 为ç´æ¥å¼ç¨äºc++ä¸çthreadï¼æäºå®ç°ç´æ¥åå©äºå®ã
老生常谈线程基础的几个问题
实现线程只有一种方式
我们知道启动线程至少可以通过以下四种方式:
实现Runnable接口
继承Thread类
线程池创建线程
带返回值的Callable创建线程
但是看它们的底层就一种方式,就是源码通过newThread()实现,其他的源码只不过在它的上面做了层封装。
实现Runnable接口要比继承Thread类的源码更好:
结构上分工更明确,线程本身属性和任务逻辑解耦。源码多周期九转公式源码
某些情况下性能更好,源码直接把任务交给线程池执行,源码无需再次newThread()。源码
可拓展性更好:实现接口可以多个,源码而继承只能单继承。源码
有的源码时候可能会问到启动线程为什么是start()方法,而不是源码run()方法,这个问题很简单,源码执行run()方法其实就是源码在执行一个类的普通方法,并没有启动一个线程,而start()方法点进去看是一个native方法。
当我们在执行java中的start()方法的时候,它的底层会调JVM由c++编写的代码Thread::start,然后c++代码再调操作系统的create_thread创建线程,创建完线程以后并不会马上运行,要等待CPU的调度。CPU的渠道标识源码调度算法有很多,比如先来先服务调度算法(FIFO),最短优先(就是对短作业的优先调度)、时间片轮转调度等。如下图所示:
线程的状态在Java中线程的生命周期中一共有6种状态。
NEW:初始状态,线程被构建,但是还没有调用start方法
RUNNABLE:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为运行中
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权
WAITING:等待状态
TIMED_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕
当然这也不是我说的,源码中就是这么定义的:
publicenumState{ /***Threadstateforathreadwhichhasnotyetstarted.*/NEW,/***Threadstateforarunnablethread.Athreadintherunnable*stateisexecutingintheJavavirtualmachinebutitmay*bewaitingforotherresourcesfromtheoperatingsystem*suchasprocessor.*/RUNNABLE,/***Threadstateforathreadblockedwaitingforamonitorlock.*Athreadintheblockedstateiswaitingforamonitorlock*toenterasynchronizedblock/methodor*reenterasynchronizedblock/methodaftercalling*{ @linkObject#wait()Object.wait}.*/BLOCKED,/***Threadstateforawaitingthread.*Athreadisinthewaitingstateduetocallingoneofthe*followingmethods:*<ul>*<li>{ @linkObject#wait()Object.wait}withnotimeout</li>*<li>{ @link#join()Thread.join}withnotimeout</li>*<li>{ @linkLockSupport#park()LockSupport.park}</li>*</ul>**<p>Athreadinthewaitingstateiswaitingforanotherthreadto*performaparticularaction.**Forexample,athreadthathascalled<tt>Object.wait()</tt>*onanobjectiswaitingforanotherthreadtocall*<tt>Object.notify()</tt>or<tt>Object.notifyAll()</tt>on*thatobject.Athreadthathascalled<tt>Thread.join()</tt>*iswaitingforaspecifiedthreadtoterminate.*/WAITING,/***Threadstateforawaitingthreadwithaspecifiedwaitingtime.*Athreadisinthetimedwaitingstateduetocallingoneof*thefollowingmethodswithaspecifiedpositivewaitingtime:*<ul>*<li>{ @link#sleepThread.sleep}</li>*<li>{ @linkObject#wait(long)Object.wait}withtimeout</li>*<li>{ @link#join(long)Thread.join}withtimeout</li>*<li>{ @linkLockSupport#parkNanosLockSupport.parkNanos}</li>*<li>{ @linkLockSupport#parkUntilLockSupport.parkUntil}</li>*</ul>*/TIMED_WAITING,/***Threadstateforaterminatedthread.*Thethreadhascompletedexecution.*/TERMINATED;}下面是这六种状态的转换:
New新创建New表示线程被创建但尚未启动的状态:当我们用newThread()新建一个线程时,如果线程没有开始调用start()方法,那么此时它的状态就是New。而一旦线程调用了start(),它的状态就会从New变成Runnable。
Runnable运行状态Java中的Runable状态对应操作系统线程状态中的两种状态,分别是Running和Ready,也就是说,Java中处于Runnable状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配CPU资源。tortoisegit发布源码
如果一个正在运行的线程是Runnable状态,当它运行到任务的一半时,执行该线程的CPU被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是Runnable,因为它有可能随时被调度回来继续执行任务。
在Java中Blocked、Waiting、TimedWaiting,这三种状态统称为阻塞状态,下面分别来看下。
Blocked从上图可以看出,从Runnable状态进入Blocked状态只有一种可能,就是进入synchronized保护的代码时没有抢到monitor锁,jvm会把当前的线程放入到锁池中。当处于Blocked的线程抢到monitor锁,就会从Blocked状态回到Runnable状态。
Waiting状态我们看上图,线程进入Waiting状态有三种可能。
没有设置Timeout参数的Object.wait()方法,jvm会把当前线程放入到等待队列。歌曲评分源码
没有设置Timeout参数的Thread.join()方法。
LockSupport.park()方法。
Blocked与Waiting的区别是Blocked在等待其他线程释放monitor锁,而Waiting则是在等待某个条件,比如join的线程执行完毕,或者是notify()/notifyAll()。
当执行了LockSupport.unpark(),或者join的线程运行结束,或者被中断时可以进入Runnable状态。当调用notify()或notifyAll()来唤醒它,它会直接进入Blocked状态,因为唤醒Waiting状态的线程能够调用notify()或notifyAll(),肯定是已经持有了monitor锁,这时候处于Waiting状态的线程没有拿到monitor锁,就会进入Blocked状态,直到执行了notify()/notifyAll()唤醒它的线程执行完毕并释放monitor锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从Blocked状态回到Runnable状态。
TimedWaiting状态在Waiting上面是TimedWaiting状态,这两个状态是非常相似的,区别仅在于有没有时间限制,consul源码剖析TimedWaiting会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。
以下情况会让线程进入TimedWaiting状态。
设置了时间参数的Thread.sleep(longmillis)方法。
设置了时间参数的Object.wait(longtimeout)方法。
设置了时间参数的Thread.join(longmillis)方法。
设置了时间参数的LockSupport.parkNanos(longnanos)。
LockSupport.parkUntil(longdeadline)方法。
在TimedWaiting中执行notify()和notifyAll()也是一样的道理,它们会先进入Blocked状态,然后抢夺锁成功后,再回到Runnable状态。当然,如果它的超时时间到了且能直接获取到锁/join的线程运行结束/被中断/调用了LockSupport.unpark(),会直接恢复到Runnable状态,而无需经历Blocked状态。
Terminated终止Terminated终止状态,要想进入这个状态有两种可能。
run()方法执行完毕,线程正常退出。
出现一个没有捕获的异常,终止了run()方法,最终导致意外终止。
线程的停止interrupt我们知道Thread提供了线程的一些操作方法,比如stop(),suspend()和resume(),这些方法已经被Java直接标记为@Deprecated,这就说明这些方法是不建议大家使用的。
因为stop()会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。这种行为类似于在linux系统中执行kill-9类似,它是一种不安全的操作。
而对于suspend()和resume()而言,它们的问题在于如果线程调用suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,这样就容易导致死锁问题,因为这把锁在线程被resume()之前,是不会被释放的。
interrupt最正确的停止线程的方式是使用interrupt,但interrupt仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。
下面我们来看下例子:
publicclassInterruptExampleimplementsRunnable{ //interrupt相当于定义一个volatile的变量//volatilebooleanflag=false;publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadt1=newThread(newInterruptExample());t1.start();Thread.sleep(5);//Main线程来决定t1线程的停止,发送一个中断信号,中断标记变为truet1.interrupt();}@Overridepublicvoidrun(){ while(!Thread.currentThread().isInterrupted()){ System.out.println(Thread.currentThread().getName()+"--");}}}执行一下,运行了一会就停止了
主线程在调用t1的interrupt()之后,这个线程的中断标记位就会被设置成true。每个线程都有这样的标记位,当线程执行时,会定期检查这个标记位,如果标记位被设置成true,就说明有程序想终止该线程。在while循环体判断语句中,通过Thread.currentThread().isInterrupt()判断线程是否被中断,如果被置为true了,则跳出循环,线程就结束了,这个就是interrupt的简单用法。
阻塞状态下的线程中断下面来看第二个例子,在循环中加了Thread.sleep秒。
publicclassInterruptSleepExampleimplementsRunnable{ //interrupt相当于定义一个volatile的变量//volatilebooleanflag=false;publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadt1=newThread(newInterruptSleepExample());t1.start();Thread.sleep(5);//Main线程来决定t1线程的停止,发送一个中断信号,中断标记变为truet1.interrupt();}@Overridepublicvoidrun(){ while(!Thread.currentThread().isInterrupted()){ try{ Thread.sleep();}catch(InterruptedExceptione){ //中断标记变为falsee.printStackTrace();}System.out.println(Thread.currentThread().getName()+"--");}}}再来看下运行结果,卡主了,并没有停止。这是因为main线程调用了t1.interrupt(),此时t1正在sleep中,这时候是接收不到中断信号的,要sleep结束以后才能收到。这样的中断太不及时了,我让你中断了,你缺还在傻傻的sleep中。
Java开发的设计者已经考虑到了这一点,sleep、wait等方法可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个InterruptedException异常,同时清除中断信号,将中断标记位设置成false。
这时候有几种做法:
直接捕获异常,不做处理,e.printStackTrace();打印下信息
将异常往外抛出,即在方法上throwsInterruptedException
再次中断,代码如下,加上Thread.currentThread().interrupt();
@Overridepublicvoidrun(){ while(!Thread.currentThread().isInterrupted()){ try{ Thread.sleep();}catch(InterruptedExceptione){ //中断标记变为falsee.printStackTrace();//把中断标记修改为trueThread.currentThread().interrupt();}System.out.println(Thread.currentThread().getName()+"--");}}这时候线程感受到了,我们人为的再把中断标记修改为true,线程就能停止了。一般情况下我们操作线程很少会用到interrupt,因为大多数情况下我们用的是线程池,线程池已经帮我封装好了,但是这方面的知识还是需要掌握的。感谢收看,多多点赞~
作者:小杰博士
直播带货源码,异步处理中会处理两次请求
直播带货源码,在异步处理中会处理两次请求,这是从序列图上观察到的SpringMVC处理异步请求的方式。让我们详细解析处理过程:
HandlerAdapter的处理流程中,异步请求处理有两大环节。首先,处理第一次请求,即异步请求的开始阶段。
在invokeHandleMethod()方法的处理流程中,除了最终直接返回null的操作外,其余步骤与正常流程相同。在SpringMVC中,异步方法仅需返回值是一个Callable对象,因此参数解析与正常流程一致,不同之处在于返回值的解析流程。让我们深入探讨返回值处理的具体过程。
在异步请求执行完毕后,进行第二次请求处理。这一阶段,异步操作完成,系统将处理结果返回给客户端。
通过以上解析,我们了解到直播带货源码在异步处理中会处理两次请求的基本原理。希望本文能为您的理解提供帮助,期待后续文章的深入探讨,敬请关注。