专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

CopyOnWriteArrayList是如何保证线程安全的

  本文已收录到AndroidFamily,技术和职场问题,请关注公众号〔彭旭锐〕提问。前言
  大家好,我是小彭。
  在上一篇文章里,我们聊到了ArrayList的线程安全问题,其中提到了CopyOnWriteArrayList的解决方法。那么CopyOnWriteArrayList是如何解决线程安全问题的,背后的设计思想是什么,今天我们就围绕这些问题展开。
  本文源码基于Java8CopyOnWriteArrayList。
  小彭的Android交流群02群已经建立啦,欢迎加入
  思维导图:
  1。回顾ArrayList
  ArrayList是基于数组实现的动态数据,是线程不安全的。例如,我们在遍历ArrayList的时候,如果其他线程并发修改数组(当然也不一定是被其他线程修改),在迭代器中就会触发failfast机制,抛出ConcurrentModificationException异常。
  示例程序ListStringlistnewArrayList();list。add(xiao);list。add(peng);list。add(。);Iteratoriteratorlist。iterator();while(iterator。hasNext()){可能抛出ConcurrentModificationException异常iterator。next();}
  要实现线程安全有3种方式:方法1使用Vector容器:Vector是线程安全版本的数组容器,它会在所有方法上增加synchronized关键字(过时,了解即可);方法2使用Collections。synchronizedList包装类方法3使用CopyOnWriteArrayList容器
  Collections。synchronizedList包装类的原理很简单,就是使用synchronized加锁,源码摘要如下:
  Collections。javapublicstaticTListTsynchronizedList(ListTlist){return(listinstanceofRandomAccess?newSynchronizedRandomAccessList(list):newSynchronizedList(list));}使用synchronized实现线程安全staticclassSynchronizedListEextendsSynchronizedCollectionEimplementsListE{finalListElist;publicbooleanequals(Objecto){if(thiso)returntrue;synchronized(mutex){returnlist。equals(o);}}publicinthashCode(){synchronized(mutex){returnlist。hashCode();}}publicEget(intindex){synchronized(mutex){returnlist。get(index);}}publicEset(intindex,Eelement){synchronized(mutex){returnlist。set(index,element);}}publicvoidadd(intindex,Eelement){synchronized(mutex){list。add(index,element);}}publicEremove(intindex){synchronized(mutex){returnlist。remove(index);}}。。。}
  如果我们将ArrayList替换为CopyOnWriteArrayList,即使其他线程并发修改数组,也不会抛出ConcurrentModificationException异常,这是为什么呢?2。CopyOnWriteArrayList的特点
  CopyOnWriteArrayList和ArrayList都是基于数组的动态数组,封装了操作数组时的搬运和扩容等逻辑。除此之外,CopyOnWriteArrayList还是用了基于加锁的读写分离和写时复制的方案解决线程安全问题:思想1读写分离(ReadWriteSplitting):将对资源的读取和写入操作分离,使得读取和写入没有依赖,在读多写少的场景中能有效减少资源竞争;思想2写时复制(CopyOnWrite,COW):在写入数据时,不直接在原数据上修改,而是复制一份新数据后写入到新数据,最后再替换到原数据的引用上。这个特性各有优缺点:优点1延迟处理:在没有写入操作时不会复制分配资源,能够避免瞬时的资源消耗。例如操作系统的fork操作也是一种写时复制的思想;优点2降低锁颗粒度:在写的过程中,读操作不会被影响,读操作也不需要加锁,锁的颗粒度从整个列表降低为写操作;缺点1弱数据一致性:在读的过程中,如果数据被其他线程修改,是无法实时感知到最新的数据变化的;缺点2有内存压力:在写操作中需要复制原数组,在复制的过程中内存会同时存在两个数组对象(只是引用,数组元素的对象还是只有一份),会带来内存占用和垃圾回收的压力。如果是写多读少的场景,就不适合。
  所以,使用CopyOnWriteArrayList的场景一定要保证是读多写少且数据量不大的场景,而且在写入数据的时候,要做到批量操作。否则每个写入操作都会触发一次复制,想想就可怕。举2个例子:例如批量写入一组数据,要使用addAll方法批量写入;例如在做排序时,要先输出为ArrayList,在ArrayList上完成排序后再写回CopyOnWriteArrayList。3。CopyOnWriteArrayList源码分析
  这一节,我们来分析CopyOnWriteArrayList中主要流程的源码。3。1CopyOnWriteArrayList的属性
  ArrayList的属性很好理解,底层是一个Object数组,我要举手提问:疑问1:为什么array字段要使用volatile关键字?锁finaltransientReentrantLocklocknewReentrantLock();在Java11中,ReentrantLock被替换为synchronized锁Thelockprotectingallmutators。(WehaveamildpreferenceforbuiltinmonitorsoverReentrantLockwheneitherwilldo。)finaltransientObjectlocknewObject();底层数组疑问1:为什么array要使用volatile关键字?privatetransientvolatileObject〔〕array;
  这个问题我们在分析源码的过程中回答。有了ArrayList的分析基础,疑问也变少了,CopyOnWriteArrayList真香。3。2CopyOnWriteArrayList的构造方法
  构造器的源码不难,但小朋友总有太多的问号,举手提问:疑问2:为什么CopyOnWriteArrayList不提供初始化容量的构造器?
  这是因为CopyOnWriteArrayList建议我们使用批量操作写入数据。如果提供了带初始化容量的构造器,意味着开发者预期会一个个地写入数据,这不符合CopyOnWriteArrayList的正确使用方法。所以,不提供这个构造器才是合理的。疑问3:为什么要把E〔〕类型的入参转化为Object〔〕类型?
  如果不转化数组类型,那么在toArray()方法返回的数组中插入Object类型对象时,会抛出ArrayStoreException。
  提示:这个问题与奇怪分支的原因相同,具体分析可以看讲《Java面试题:ArrayList可以完全替代数组吗?》的文章中,这里不重复讲了。疑问2:为什么CopyOnWriteArrayList不提供预初始化容量的构造器?无参构造方法publicCopyOnWriteArrayList(){创建空数组setArray(newObject〔0〕);}带集合的构造方法publicCopyOnWriteArrayList(Collectionlt;?extendsEc){Object〔〕elements;if(c。getClass()CopyOnWriteArrayList。class)elements((CopyOnWriteArrayListlt;?)c)。getArray();else{elementsc。toArray();这个奇怪的分支在ArrayList文章中分析过,去看看if(elements。getClass()!Object〔〕。class)elementsArrays。copyOf(elements,elements。length,Object〔〕。class);}setArray(elements);}带数组的构造方法publicCopyOnWriteArrayList(E〔〕toCopyIn){疑问3:为什么要把E〔〕类型的入参转化为Object〔〕类型setArray(Arrays。copyOf(toCopyIn,toCopyIn。length,Object〔〕。class));}finalObject〔〕getArray(){returnarray;}finalvoidsetArray(Object〔〕a){arraya;}publicObject〔〕toArray(){Object〔〕elementsgetArray();returnArrays。copyOf(elements,elements。length);}3。3CopyOnWriteArrayList的写方法
  我们将CopyOnWriteArrayList的添加、删除和修改方法统一为写方法,三种写方法的模板其实是一样的:1、在写入之前先获取对象的锁;2、复制新数组;3、在新数组上完成写入操作;4、将新数组设置为底层数组;5、释放对象的锁。
  小朋友总是有太多问号,举手提问:疑问4:在添加方法中,为什么扩容只增大1容量,而ArrayList会增大1。5倍?
  这还是因为CopyOnWriteArrayList建议我们使用批量操作写入数据。ArrayList额外扩容1。5倍是为了避免每次add都扩容,而CopyOnWriteArrayList并不建议一个个地添加数据,而是建议批量操作写入数据,例如addAll方法。所以,CopyOnWriteArrayList不额外扩容才是合理的。
  另外,网上有观点看到CopyOnWriteArrayList没有限制数组最大容量,就说CopyOnWriteArrayList是无界的,没有容量限制。这显然太表面了。数组的长度限制是被虚拟机固化的,CopyOnWriteArrayList没有限制的原因是:它没有做额外扩容,而且不适合大数据的场景,所以没有限制的必要。
  最后还剩下1个问题:疑问1:为什么array字段要使用volatile关键字?
  volatile变量是Java轻量级的线程同步原语,volatile变量的读取和写入操作中会加入内存屏障,能够保证变量写入的内存可见性,保证一个线程的写入能够被另一个线程观察到。
  添加方法在数组尾部添加元素publicbooleanadd(Ee){finalReentrantLocklockthis。lock;获取锁lock。lock();复制新数组Object〔〕elementsgetArray();intlenelements。length;疑问4:在添加方法中,为什么扩容只增大1容量,而ArrayList会增大1。5倍?Object〔〕newElementsArrays。copyOf(elements,len1容量1);在新数组上添加元素newElements〔len〕e;设置新数组setArray(newElements);释放锁lock。unlock();returntrue;}在数组尾部添加元素publicvoidadd(intindex,Eelement){原理相同,省略。。。}批量在数组尾部添加元素publicbooleanaddAll(Collectionlt;?extendsEc){原理相同,省略。。。}
  修改方法修改数组元素publicEset(intindex,Eelement){finalReentrantLocklockthis。lock;获取锁lock。lock();旧元素Object〔〕elementsgetArray();EoldValueget(elements,index);if(oldValue!element){复制新数组intlenelements。length;Object〔〕newElementsArrays。copyOf(elements,len);在新数组上添加元素newElements〔index〕element;设置新数组setArray(newElements);}else{Notquiteanoop;ensuresvolatilewritesemanticssetArray(elements);}释放锁lock。unlock();返回旧数据returnoldValue;}
  删除方法删除数组元素publicEremove(intindex){finalReentrantLocklockthis。lock;获取锁lock。lock();Object〔〕elementsgetArray();intlenelements。length;旧元素EoldValueget(elements,index);intnumMovedlenindex1;if(numMoved0)删除首位元素setArray(Arrays。copyOf(elements,len1));else{删除中间元素复制新数组Object〔〕newElementsnewObject〔len1〕;System。arraycopy(elements,0,newElements,0,index);System。arraycopy(elements,index1,newElements,index,numMoved);设置新数组setArray(newElements);}释放锁lock。unlock();返回旧数据returnoldValue;}3。4CopyOnWriteArrayList的读取方法
  可以看到读取方法并没有加锁。privateEget(Object〔〕a,intindex){return(E)a〔index〕;}publicEget(intindex){returnget(getArray(),index);}publicbooleancontains(Objecto){Object〔〕elementsgetArray();returnindexOf(o,elements,0,elements。length)0;}3。5CopyOnWriteArrayList的迭代器
  CopyOnWriteArrayList的迭代器COWIterator是弱数据一致性的,所谓数据一致性问题讨论的是同一份数据在多个副本之间的一致性问题,你也可以理解为多个副本的状态一致性问题。例如内存与多核心Cache副本之间的一致性,或者数据在主从数据库之间的一致性。
  提示:关于数据一致性和顺序一致性的区别,在小彭的计算机组成原理专栏讨论过《已经有MESI协议,为什么还需要volatile关键字?》,去看看。
  为什么是弱的呢?这是因为COWIterator迭代器会持有CopyOnWriteArrayList底层数组的引用,而CopyOnWriteArrayList的写入操作是写入到新数组,因此COWIterator是无法感知到的,除非重新创建迭代器。
  相较之下,ArrayList的迭代器是通过持有外部类引用的方式访问ArrayList的底层数组,因此在ArrayList上的写入操作会实时被迭代器观察到。
  CopyOnWriteArrayList。java注意看:有static关键字,直接引用底层数组staticfinalclassCOWIteratorEimplementsListIteratorE{底层数组privatefinalObject〔〕snapshot;privateintcursor;privateCOWIterator(Object〔〕elements,intinitialCursor){cursorinitialCursor;snapshotelements;}}
  ArrayList。java注意看:没有static关键字,通过外部类引用来访问底层数组privateclassItrimplementsIteratorE{intcursor;indexofnextelementtoreturnintlastRet1;indexoflastelementreturned;1ifnosuchintexpectedModCountmodCount;Itr(){}。。。}3。6CopyOnWriteArraySet的序列化过程
  与ArrayList类似,CopyOnWriteArraySet也重写了JDK序列化的逻辑,只把elements数组中有效元素的部分序列化,而不会序列化整个数组。
  同时,ReentrantLock对象是锁对象,序列化没有意义。在反序列化时,会通过resetLock()设置一个新的ReentrantLock对象。序列化过程privatevoidwriteObject(java。io。ObjectOutputStreams)throwsjava。io。IOException{s。defaultWriteObject();Object〔〕elementsgetArray();写入数组长度s。writeInt(elements。length);写入有效元素for(Objectelement:elements)s。writeObject(element);}反序列化过程privatevoidreadObject(java。io。ObjectInputStreams)throwsjava。io。IOException,ClassNotFoundException{s。defaultReadObject();设置ReentrantLock对象resetLock();读取数组长度intlens。readInt();SharedSecrets。getJavaOISAccess()。checkArray(s,Object〔〕。class,len);创建底层数组Object〔〕elementsnewObject〔len〕;读取数组对象for(inti0;ilen;i)elements〔i〕s。readObject();设置新数组setArray(elements);}疑问5:resetLock()方法不好理解,解释一下?privatevoidresetLock(){等价于带Volatile语义的this。locknewReentrantLock()UNSAFE。putObjectVolatile(this,lockOffset,newReentrantLock());}UnsafeAPIprivatestaticfinalsun。misc。UnsafeUNSAFE;lock字段在对象实例数据中的偏移量privatestaticfinallonglockOffset;static{这三行的作用:lock字段在对象实例数据中的偏移量UNSAFEsun。misc。Unsafe。getUnsafe();Classlt;?kCopyOnWriteArrayList。class;lockOffsetUNSAFE。objectFieldOffset(k。getDeclaredField(lock));}
  小朋友又是有太多问号,举手提问:疑问5:resetLock()方法不好理解,解释一下?
  在static代码块中,会使用UnsafeAPI获取CopyOnWriteArrayList的lock字段在对象实例数据中的偏移量。由于字段的偏移是全局固定的,所以这个偏移量可以记录在static字段lockOffset中。
  在resetLock()中,通过UnSafeAPIputObjectVolatile将新建的ReentrantLock对象设置到CopyOnWriteArrayList的lock字段中,等价于带volatile语义的this。locknewReentrantLock(),保证这个字段的写入具备内存可见性。
  字段的偏移量是什么意思呢?简单来说,普通对象和Class对象的实例数据区域是不同的:1、普通对象:包括当前类声明的实例字段以及父类声明的实例字段,不包括类的静态字段。UnSafeAPIobjectFieldOffset(Filed)就是获取了参数Filed在实例数据中的偏移量,后续就可以通过这个偏移量为字段赋值;2、Class对象:包括当前类声明的静态字段和方法表等。
  对象内存布局
  提示:关于字段的偏移量,我们在《对象的内存分为哪几个部分?》这篇文章里讨论过,去看看。3。7CopyOnWriteArraySet的clone()过程
  CopyOnWriteArraySet的clone()很巧妙。按照正常的思维,CopyOnWriteArraySet中的array数组是引用类型,因此在clone()中需要实现深拷贝,否则原对象与克隆对象就会相互影响。但事实上,array数组并没有被深拷贝,哇点解啊?疑问6:为什么array数组没有深拷贝?
  这就是因为CopyOnWrite啊!没有Write为什么要Copy呢?(我觉得已经提醒到位了,只要你仔细阅读前文对CopyOnWrite的论证,你一定会懂的。要是是在不懂,私信我吧)publicObjectclone(){try{SuppressWarnings(unchecked)疑问6:为什么array数组没有深拷贝?CopyOnWriteArrayListEclone(CopyOnWriteArrayListE)super。clone();设置ReentrantLock对象(相当于lock字段的深拷贝)clone。resetLock();returnclone;}catch(CloneNotSupportedExceptione){thisshouldnthappen,sinceweareCloneablethrownewInternalError();}}4。CopyOnWriteArraySet源码分析
  在Java标准库中,还提供了一个使用COW思想的Set集合CopyOnWriteArraySet。
  CopyOnWriteArraySet和HashSet都是继承于AbstractSet的,但CopyOnWriteArraySet是基于CopyOnWriteArrayList动态数组的,并没有使用哈希思想。而HashSet是基于HashMap散列表的,能够实现O(1)查询。4。1CopyOnWriteArraySet的构造方法
  看一下CopyOnWriteArraySet的构造方法,底层就是有一个CopyOnWriteArrayList动态数组。
  CopyOnWriteArraySet。javapublicclassCopyOnWriteArraySetEextendsAbstractSetEimplementsjava。io。Serializable{底层就是OnWriteArrayListprivatefinalCopyOnWriteArrayListEal;无参构造方法publicCopyOnWriteArraySet(){alnewCopyOnWriteArrayListE();}带集合的构造方法publicCopyOnWriteArraySet(Collectionlt;?extendsEc){if(c。getClass()CopyOnWriteArraySet。class){入参是CopyOnWriteArraySet,说明是不重复的,直接添加CopyOnWriteArraySetEcc(CopyOnWriteArraySetE)c;alnewCopyOnWriteArrayListE(cc。al);}else{使用addAllAbsent添加不重复的元素alnewCopyOnWriteArrayListE();al。addAllAbsent(c);}}publicintsize(){returnal。size();}}4。2CopyOnWriteArraySet的操作方法
  CopyOnWriteArraySet的方法基本上都是交给CopyOnWriteArraySet代理的,由于没有使用哈希思想,所以操作的时间复杂度是O(n)。
  CopyOnWriteArraySet。javapublicbooleanadd(Ee){returnal。addIfAbsent(e);}publicbooleancontains(Objecto){returnal。contains(o);}
  CopyOnWriteArrayList。javapublicbooleanaddIfAbsent(Ee){Object〔〕snapshotgetArray();returnindexOf(e,snapshot,0,snapshot。length)0?false:addIfAbsent(e,snapshot);}publicbooleancontains(Objecto){Object〔〕elementsgetArray();returnindexOf(o,elements,0,elements。length)0;}通过线性扫描匹配元素位置,而不是计算哈希匹配,时间复杂度是O(n)privatestaticintindexOf(Objecto,Object〔〕elements,intindex,intfence){if(onull){for(intiindex;ifence;i)if(elements〔i〕null)returni;}else{for(intiindex;ifence;i)if(o。equals(elements〔i〕))returni;}return1;}5。总结1、CopyOnWriteArrayList和ArrayList都是基于数组的动态数组,封装了操作数组时的搬运和扩容等逻辑;2、CopyOnWriteArrayList还是读写分离和写时复制的方案解决线程安全问题;3、使用CopyOnWriteArrayList的场景一定要保证是读多写少且数据量不大的场景,而且在写入数据的时候,要做到批量操作;4、CopyOnWriteArrayList的迭代器是弱数据一致性的的,迭代器会持有底层数组的引用,而CopyOnWriteArrayList的写入操作是写入到新数组,因此迭代器是无法感知到的;5、CopyOnWriteArraySet是基于CopyOnWriteArrayList动态数组的,并没有使用哈希思想。

合众社俄罗斯飞船前往国际空间站救援因返回舱泄露被困宇航员中国小康网02月22日讯老马俄罗斯航天局Roscosmos将联盟号宇宙飞船送往国际空间站,将三名宇航员带回地球的延迟救援任务计划,火箭于美国东部时间周四晚上从哈萨克斯坦拜科努尔航天悟空教程增加杂色和智能降噪,零基础也能轻松上手!Hello,大家好,这里是空空!又到了悟空的课堂教学了,本期我们讲解悟空特效中的增减杂色,一起来看看吧!大家都知道我们使用智能手机拍照功能拍摄出来的照片跟使用相机拍摄出来的照片,在全自动化全方位内网综合扫描漏洞扫描开源工具Fscan今天给大家分享一个开源的内网扫描工具,在GitHub平台上面有5。3KStar。具体情况如下简介fscan是一款go语言编写的内网综合扫描工具,方便一键自动化全方位漏扫扫描。可以让第3支解散队曝光步江苏后尘,球队停运自费集训,等着足协救命国内三级联赛目前有多家俱乐部陷入困境,如果问题迟迟得不到解决,那么最后坚持不下去就很可能走上解散的道路。目前已经有两支俱乐部官宣过解散的消息,从中超降级的武汉长江已经解散,第二支解人类要飞出太阳系有多难?人类要飞出太阳系有多难?飞出太阳系对人类来说是一个巨大而困难的挑战。目前为止,人类尚未成功实现这一目标,但已经有了一些探索太阳系边缘和进入星际空间的初步尝试。飞出太阳系需要克服多种赵露思状告科大讯飞,明天开庭!双方都有来头,后者老板身家85亿企查查显示,因为肖像权纠纷,赵露思将科大讯飞股份有限公司告上法庭,并将于2月23日在成都铁路运输法院开庭审理,也就是明天。此前,科大讯飞旗下产品讯飞输入法,曾因上线赵露思皮肤而引发聚焦数字经济合作谱写智慧城市篇章东盟媒体看数字中国主题采访系列活动走进深圳当前,数字经济智慧城市成为促进经济复苏推动可持续发展的重要引擎。2月20日,以聚焦数字经济合作谱写智慧城市篇章为主题的东盟媒体看数字中国主题采访系列活动在深圳开启。媒体团成员将通过最终参赛队会是几支?准入初审进行中,已有2支球队退出直播吧2月21日讯随着新疆天山雪豹退出职业联赛,新赛季中国职业联赛已有2支球队退出。此前,足协宣布2023年2月15日至2023年3月5日进行新赛季中超中甲和中乙联赛的准入工作,而中国和欧洲在2025年将合作进行太阳探测卫星测试这标志着中国制造的卫星首次运往欧洲航天局,并由中国团队帮助在欧空局设施进行组装和测试预计SMILE将于2025年4月左右从位于南美的欧洲航天港搭载VegaC火箭发射升空在一项史无前癸卯正月廿日记癸卯正月廿日记一笑今晨,我象一根枝条抖掉了冬日的枯叶立在险峻的峭崖向远方动情地呼唤春天,你的孩子回一一来一一了声音,在空中波动漫延云雾,从脚下慢慢移开小鸟喳喳,开始向周围的丛林聚集无题很想为你写书,却总提笔忘情你在每个独处的空间里,如影随形像和自己的影子谈了一场轰轰烈烈的爱恋是也不是如果是,相爱就确认无疑,无比确信可惜不是,猜疑不确定从不缺席或许是奢求太多才分崩
郑州一高校副院长工作群发色情信息,称被盗号,分析几分钟能干啥5月19日,网曝郑州轻工业大学体育学院副院长谭某某在工作群发色情内容。之后谭某某随后解释称,账号被盗,刚通过警方找回。而20日,体育学院回应称,谭某某账号确实被盗,正在补救。真的么关注全球最昂贵汽车诞生,成交价近10亿元文懂车帝原创邢秋鸿懂车帝原创行业近日,一台奔驰300SLRUhlenhautCoup以1。35亿欧元(约合人民币9。58亿)的价格成交,打破了此前法拉利250GTO的成交记录(482013年,贵州女子杨丽,将一片树叶卖到7000元,靠树叶年赚上千万这世上并不缺少美,只是缺少发现美的眼睛这世上并不缺少赚钱的机会,只是缺少去尝试的人。你可曾想过一片普通的树叶,竟然能卖7000元,还能靠树叶年赚上千万?但杨丽就做到了。努力上进的贵历代禁书闻见录(二)剪灯余话剪灯余话是明代官员李昌祺著明代文言短篇小说集,成书于明代永乐十七年(1419)。该小说集多取材于元末明初,以婚姻爱情故事为主,亦有幽冥灵异人物。全书内容丰富,情节曲折,词采绚烂。该今日小满,今夏热不热雨水多不多?全看小满是阴还是晴,咋回事前言今日小满,麦已小熟,天未大热,乐享小满。今夏热不热雨水多不多?全看小满是阴还是晴,咋回事?老话说得好清和入序殊无暑,小满先时政有雷,进入小满后,气温不断升高,空气中的湿度也是越去贵州必点的6道名菜,是黔菜之魂,老板都以为你是贵州人水依长江之源,山附昆仑之尾。这句话说的正是贵州,一个冬暖夏凉,风景秀丽的地方。平常人都知道贵州风景好,有黄果树瀑布,有各种各样的风景。但贵州只有风景亮眼吗?并不是,贵州的美食也一样记者曼联队内仅博格巴没有降薪条款,此前踢欧联周薪仍是29万镑直播吧5月21日讯据曼联跟队记者LaurieWhitwell消息,博格巴是队内唯一一个没有降薪条款的球员。因为无缘下赛季欧冠,曼联球员面临25的降薪,这是伍德沃德和贾奇实施的政策,上世纪,风靡全国的宝塔糖,为何突然消失?背后教训不能忘科学研究的进展及其日益扩充的领域将唤起我们的希望。诺贝尔科学的发展是国家的第一发展力,早在之前,国家因为没有发展完善的科技,连一棵小小的植物都需要进口,而这棵植物便是治病的宝塔糖的放假通知来了不调休!不免费数着日历一算距离端午假期只剩14天不过要告诉大家的是将要到来的端午假期不!调!休!哟!端午节放假安排根据国务院发布的关于2022年部分节假日安排的通知2022年端午节放假时间为6月波音飞船对接国际空间站,地面拍到惊险瞬间,下次有人敢坐吗?在经过约9个小时的折腾之后,最新的图片显示,波音发射的星际线飞船成功对接国际空间站,终于是一扫3年前第一次发射时因燃料消耗太多,还没到目的地就要返航的尴尬。不过,波音飞船的这次任务不负青春搏一场琼中县红毛希望小学李雷岁月不居时节如流,作为一名新时代的追梦人,我们以青春来绽放希望之光,因为相信蝴蝶煽动着翅膀能带来风暴,水滴也能折射出太阳光的灿烂,所以青年人要以我是不一样的烟
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网