本篇文章给大家谈谈视频网站源码分享java,以及小视频网站源代码对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。
图片标题
从不浪费时间的人,没有工夫抱怨时间不够。——杰弗逊
0前言
SynchronousQueue一个阻塞队列,其中每个插入操作必须等待另一个线程进行相应的删除操作,反之亦然。同步队列没有任何内部容量,甚至没有一个容量。你无法窥视SynchronousQueue,因为仅当你尝试删除它时,该元素才存在。你不能插入元素(使用任何方法),除非另一个线程试图将其删除;你无法进行迭代,因为没有要迭代的内容。队列的头部是第一个排队的插入线程试图添加到队列中的元素;如果没有这样的排队线程,则没有元素可用于删除,并且poll()将返回null。为了其他Collection方法(例如,contains)的目的,SynchronousQueue充当空集合。此队列不允许空元素.
同步队列类似于CSP和Ada中使用的集合通道。它们非常适合切换设计,在该设计中,在一个线程中运行的对象必须与在另一个线程中运行的对象同步,以便向其传递一些信息,事件或任务。
此类支持可选的公平性策略,用于订购正在等待的生产者和使用者线程。默认情况下,不保证此排序。但是,将公平性设置为true构造的队列将按FIFO顺序授予线程访问权限。
此类及其迭代器实现Collection和Iterator接口的所有可选方法。
此类是JavaCollectionsFramework的成员。
1继承体系
图片标题
继承AbstractQueue抽象类,定义了对队列的基本操作实现BlockingQueue阻塞队列接口,其对队列的操作可能会抛出异常实现Searializable接口,可以被序列化
2数据结构
由于SynchronousQueue的支持公平策略和非公平策略,所以底层有两种数据结构
队列(实现公平策略),有一个头结点和尾结点栈(实现非公平策略),有一个头结点
队列与栈都是通过链表来实现的。具体的数据结构如下
图片标题
内部类UML图
Transferer是TransferStack栈和TransferQueue队列的公共类,定义了转移数据的公共操作,由TransferStack和TransferQueue具体实现图片标题WaitQueue、LifoWaitQueue、FifoWaitQueue表示为了兼容JDK1.5版本中的SynchronousQueue的序列化策略所遗留的,这里不做具体的讲解图片标题
3非公平的堆栈(默认策略)
3.1栈元素
put的时候,就往栈中放数据。take的时候,就从栈中取数据,两者操作都是在栈顶上操作数据.
volatileSNodenext栈顶的下一个节点volatileSNodematch匹配,用来判断阻塞栈元素能被唤醒的时机比如我们先执行take,此时队列中没有数据,take被阻塞了,栈元素为SNode1当put时,会把当前put的栈元素赋值给SNode1的match属性,并唤醒take当take被唤醒,发现SNode1的match属性有值时,就能拿到put的数据volatileThreadwaiter阻塞的线程Objectitem未投递/未消费的消息
3.2入栈和出栈
入栈使用put等方法,将数据放到栈中图片标题出栈使用take等方法,把数据从栈中拿出来图片标题
操作的对象都是栈顶,底层实现的方法也是同一个:
@SuppressWarnings(&34;)\nEtransfer(Ee,booleantimed,longnanos){\nSNodes=null;//constructed/reusedasneeded\n\n//e为空:take方法,非空:put方法\nintmode=(e==null)?REQUEST:DATA;\n\n//自旋\nfor(;;){\n//头节点情况分类\n//1:为空,说明队列中还没有数据\n//2:非空,并且是take类型的,说明头节点线程正等着拿数据\n//3:非空,并且是put类型的,说明头节点线程正等着放数据\nSNodeh=head;\n\n//栈头为空,说明队列中还没有数据。\n//栈头非空且栈头的类型和本次操作一致\n//\t比如都是put,那么就把本次put操作放到该栈头的前面即可,让本次put能够先执行\nif(h==null||h.mode==mode){//emptyorsame-mode\n//设置了超时时间,并且e进栈或者出栈要超时了,\n//就会丢弃本次操作,返回null值。\n//如果栈头此时被取消了,丢弃栈头,取下一个节点继续消费\nif(timed&&nanos<=0){//无法等待\n//栈头操作被取消\nif(h!=null&&h.isCancelled())\n//丢弃栈头,把栈头的后一个元素作为栈头\ncasHead(h,h.next);//将取消的节点弹栈\n//栈头为空,直接返回null\nelse\nreturnnull;\n//没有超时,直接把e作为新的栈头\n}elseif(casHead(h,s=snode(s,e,h,mode))){\n//e等待出栈,一种是空队列take,一种是put\nSNodem=awaitFulfill(s,timed,nanos);\nif(m==s){//waitwascancelled\nclean(s);\nreturnnull;\n}\n//本来s是栈头的,现在s不是栈头了,s后面又来了一个数,把新的数据作为栈头\nif((h=head)!=null&&h.next==s)\ncasHead(h,s.next);//helps&39;smatch\nif(m==null){//allwaitersaregone\ncasHead(s,null);//popfulfillnode\ns=null;//usenewnodenexttime\nbreak;//restartmainloop\n}\nSNodemn=m.next;\n//tryMatch非常重要的方法,两个作用:\n//1唤醒被阻塞的栈头m,2把当前节点s赋值给m的match属性\n//这样栈头m被唤醒时,就能从m.match中得到本次操作s\n//其中s.item记录着本次的操作节点,也就是记录本次操作的数据\nif(m.tryMatch(s)){\ncasHead(s,mn);//popbothsandm\nreturn(E)((mode==REQUEST)?m.item:s.item);\n}else//lostmatch\ns.casNext(m,mn);//helpunlink\n}\n}\n}else{//helpafulfiller\nSNodem=h.next;//mish&39;edtoorfromnull当前元素的值,如果当前元素被阻塞住了,等其他线程来唤醒自己时,其他线程会把自己set到item里面volatileThreadwaiter//tocontrolpark/unpark阻塞线程finalbooleanisDatatrue是put,false是take
4.2transfer
TransferQueue内部类的transfer方法
Etransfer(Ee,booleantimed,longnanos){\n/**\n*\n*这个基本方法,主要分为两种情况\n*\n*1.若队列为空/队列中的尾节点和自己的类型相同,则添加node\n*到队列中,直到timeout/interrupt/其他线程和这个线程匹配\n*timeout/interruptawaitFulfill方法返回的是node本身\n*匹配成功的话,要么返回null(producer返回的),或正真的传递值(consumer返回的)\n*\n*2.队列不为空,且队列的head.next节点是当前节点匹配的节点,\n*进行数据的传递匹配,并且通过advanceHead方法帮助先前block的节点dequeue\n*/\n\nQNodes=null;//根据需要构造/重用\n//true:putfalse:get\nbooleanisData=(e!=null);\n\nfor(;;){\n//队列首尾的临时变量,队列空时,t=h\nQNodet=tail;\nQNodeh=head;\nif(t==null||h==null)//看到未初始化的值\ncontinue;//自旋\n//首尾节点相同,队列空\n//或队尾节点的操作和当前节点操作相同\nif(h==t||t.isData==isData){\nQNodetn=t.next;\n//tail被修改,重试\nif(t!=tail)\ncontinue;\n//队尾后面的值还不为空,说明其他线程添加了tail.next,t还不是队尾,直接把tn赋值给t\nif(tn!=null){\nadvanceTail(t,tn);\n//自旋\ncontinue;\n}\n//超时直接返回null\nif(timed&&nanos<=0)//等不及了\nreturnnull;\n//创建节点\nif(s==null)\ns=newQNode(e,isData);\n//如果把s放到队尾失败,继续递归放进去\nif(!t.casNext(null,s))//链接失败\ncontinue;\n\nadvanceTail(t,s);//推进tail节点并等待\n//阻塞住自己,直到有其他线程与之匹配,或它自己进行线程的中断\nObjectx=awaitFulfill(s,e,timed,nanos);\nif(x==s){//waitwascancelled\nclean(t,s);//对接点s进行清除,若s不是链表的最后一个节点,则直接CAS进行节点的删除,若s是链表的最后一个节点,则要么清除以前的cleamMe节点(cleamMe!=null),然后将s.prev设置为cleanMe节点,下次进行删除或直接将s.prev设置为cleanMe\nreturnnull;\n}\n\nif(!s.isOffList()){//尚未取消链接\nadvanceHead(t,s);//unlinkifhead推进head节点,下次就调用s.next节点进行匹配(这里调用的是advanceHead,因为代码能执行到这边说明s已经是head.next节点了)\nif(x!=null)//andforgetfields\ns.item=s;\ns.waiter=null;\n}\nreturn(x!=null)?(E)x:e;\n//队列不为空,并且当前操作和队尾不一致\n//也就是说当前操作是队尾是对应的操作\n//比如说队尾是因为take被阻塞的,那么当前操作必然是put\n}el***plementary-mode\n//如果是第一次执行,此处的m代表就是tail\n//也就是这行代码体现出队列的公平,每次操作时,从头开始按照顺序进行操作\nQNodem=h.next;//nodetofulfill\nif(t!=tail||m==null||h!=head)\ncontinue;//inconsistentread\n\nObjectx=m.item;\nif(isData==(x!=null)||//malreadyfulfilled\nx==m||//mcancelled\n//m代表栈头\n//这里把当前的操作值赋值给阻塞住的m的item属性\n//这样m被释放时,就可得到此次操作的值\n!m.casItem(x,e)){//lostCAS\nadvanceHead(h,m);//dequeueandretry\ncontinue;\n}\n//当前操作放到队头\nadvanceHead(h,m);//successfullyfulfilled\n//释放队头阻塞节点\nLockSupport.unpark(m.waiter);\nreturn(x!=null)?(E)x:e;\n}\n}\n}\n
线程被阻塞住后,当前线程是如何把自己的数据传给阻塞线程的。假设线程1从队列中take数据,被阻塞,变成阻塞线程A然后线程2开始往队列中put数据B,大致的流程如下:
线程1从队列take数据,发现队列内无数据,于是被阻塞,成为A线程2往队尾put数据,会从队尾往前找到第一个被阻塞的节点,假设此时能找到的就是节点A,然后线程B把将put的数据放到节点A的item属性里面,并唤醒线程1线程1被唤醒后,就能从A.item里面拿到线程2put的数据了,线程1成功返回。
在这个过程中,公平主要体现在,每次put数据的时候,都put到队尾上,而每次拿数据时,并不是直接从堆头拿数据,而是从队尾往前寻找第一个被阻塞的线程,这样就会按照顺序释放被阻塞的线程。
avanceTail
尝试cas将nt作为新的tail图片标题
4.3图解公平队列模型
公平模式下,底层实现使用的是TransferQueue队列,它有一个head和tail指针,用于指向当前正在等待匹配的线程节点。
初始化时的TransferQueue线程put1执行put(1),由于当前没有配对的消费线程,所以put1线程入队,自旋一小会后睡眠等待接着,线程put2执行put(2),put2线程入队,自旋一小会后睡眠等待这时来了一个线程take1,执行了take,由于tail指向put2线程,put2线程跟take1线程匹配,这时take1线程不需要入队注意了!这时要唤醒的线程并不是put2,而是put1.因为现在是公平策略,谁先入队,谁优先被唤醒,这里显然put1应优先被唤醒.公平策略总结一句话就是:队尾匹配队头出队执行后put1线程被唤醒,take1线程的take()方法返回了1(put1线程的数据),这样就实现了线程间的一对一通信最后,再来一个线程take2,执行take操作,这时候只有put2线程在等候,而且两个线程匹配上了,线程put2被唤醒,take2线程take操作返回了2(线程put2的数据),这时候队列又回到了起点
5总结
SynchronousQueue内没有容器为什么还能够存储一个元素呢?因为内部没有容器指的是没有像数组那样的内存空间存多个元素,但是是有单地址内存空间,用于交换数据.SynchronousQueue凭借其独有的线程配对通信机制,在大部分平常开发中,可能都不太会用到,但线程池技术中会有所使用,由于内部没有使用AQS,而是直接使用CAS,所以代码理解起来会比较困难,但这并不妨碍我们理解底层的实现模型,在理解了模型的基础上,再翻阅源码,就会有方向感,看起来也会比较容易!
视频网站源码分享java和小视频网站源代码的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!
