游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

面试必问的ThreadLocal细节,其他文章里写的真的对吗

  Freemen
  FreemenApp是一款专注于IT程序员求职招聘的一个求职平台,旨在帮助IT技术工作者能更好更快入职及努力协调IT技术者工作和生活的关系,让工作更自由!
  ThreadLocal的作用是什么?使用时有哪些注意事项?为什么ThreadLocalMap中的Entry要使用WeakReference?netty中FastThreadLocal又做了什么优化?答案尽在本文中。ThreadLocal介绍
  用ThreadLocal修饰的变量,一般我们称为线程本地变量。那么一般什么情况下会使用ThreadLocal呢?解决线程安全问题。线程安全问题一般是多个线程对共享可变变量变量的修改问题,那么如果线程之间不共享变量,自然就解决了这个问题,通过线程本地变量就可以不共享,每个线程只能获取到自己的线程本地变量,线程间互不打扰。比如java。text。SimpleDateFormat不是线程安全的,如果多个线程都使用同一个SimpleDateFormat对象进行日期操作,则会出现线程安全问题。一种解决方案就是把SimpleDateFormat对象进行ThreadLocal封装,代码示例如下。privatestaticfinalThreadLocalSimpleDateFormatsimpleDateFormatThreadLocal。withInitial(SimpleDateFormat::new);publicvoidsay(){simpleDateFormat。get()。format(newDate());}同一线程内代码间传递上下文信息在一些框架中,需要在不同的代码间传递信息,比如分布式追踪(tracing)框架一般都需要在各个开源框架中进行埋点上报,比如上报一个请求处理开始和请求处理结束的埋点,但是如果开始和请求在不同的方法中,该怎么不修改框架方法参数实现埋点串联id的传递呢?通过通过ThreadLocal我们就可以实现id的透传;spring在aop、事务等功能中都使用了ThreadLocal来进行线程内方法间的对象传递;在业务代码中,也经常会用ThreadLocal传递一些公共参数比如请求的用户id,这样可以减少参数的传递。ThreadLocal的方法和使用介绍
  ThreadLocal提供的方法有get(),set(Tvalue),remove(),并且有一个可以override的initialValue()方法。
  方法
  方法介绍
  get
  获取这个ThreadLocal在当前线程内的值
  set(Tvalue)
  设置这个ThreadLocal在当前线程内的值
  remove
  删除这个ThreadLocal在当前线程内的值
  initialValue()
  如果这个ThreadLocal在当前线程内没有值,会通过initialValue()进行初始化,默认返回null。可以通过匿名内部类或Thread。withInitial(Supplierlt;?extendsSsupplier)实现overrideinitialValue()方法
  一般我们定义ThreadLocal变量都定义成staticfinal的变量,然后就可以通过这个ThreadLocal变量进行getset了。privatestaticfinalThreadLocalSimpleDateFormatsimpleDateFormatThreadLocal。withInitial(SimpleDateFormat::new);publicvoidsay(){simpleDateFormat。get()。format(newDate());}ThreadLocal实现
  了解了ThreadLocal的作用后,我们开始分析一下内部实现。
  首先,每个线程Thread对象内有一个ThreadLocalMap类型的threadLocals字段,里面保存着key为ThreadLocal到value为Object的映射。classThread{ThreadLocalvaluespertainingtothisthread。ThismapismaintainedbytheThreadLocalclass。ThreadLocal。ThreadLocalMapthreadLocalsnull;}
  ThreadLocal的get,set,remove方法都是通过操作当前线程内的ThreadLocalMap字段实现的,操作的key为自己(this)publicclassThreadLocalT{每个ThreadLocal的hashCode,用来在ThreadLocalMap中寻找自己的位置。ThreadLocalMap使用的是线性探查的开放寻址法解决hash冲突而不是HashMap中的链表法privatefinalintthreadLocalHashCodenextHashCode();用来生成分配ThreadLocal的hashCode的AtomicIntegerprivatestaticAtomicIntegernextHashCodenewAtomicInteger();AtomicInteger每次增加的间隔,0x61c88647这个魔数在映射到2的次方的数组中能够保证比较高好的分散性,减少冲突https:stackoverflow。comquestions38994306whatisthemeaningof0x61c88647constantinthreadlocaljavaprivatestaticfinalintHASHINCREMENT0x61c88647;生成hashCode,在ThreadLocal对象构造的时候(构造代码块中)调用privatestaticintnextHashCode(){returnnextHashCode。getAndAdd(HASHINCREMENT);}覆盖initialValue()可以定制ThreadLocal的默认值protectedTinitialValue(){returnnull;}publicThreadLocal(){}publicTget(){获取当前线程Thread对象ThreadtThread。currentThread();从Thread对象拿到ThreadLocalMap字段ThreadLocalMapmapgetMap(t);如果map不为空,从map中尝试获取当前ThreadLocal对应的value值if(map!null){ThreadLocalMap。Entryemap。getEntry(this);if(e!null){SuppressWarnings(unchecked)Tresult(T)e。value;returnresult;}}map为空,或者map中没有找到当前ThreadLocal对应的value的映射,则会进行创建ThreadLocalMap、在map中设置初始默认值returnsetInitialValue();}privateTsetInitialValue(){TvalueinitialValue();ThreadtThread。currentThread();ThreadLocalMapmapgetMap(t);if(map!null){map。set(this,value);}else{createMap(t,value);}if(thisinstanceofTerminatingThreadLocal){TerminatingThreadLocal。register((TerminatingThreadLocallt;?)this);}returnvalue;}publicvoidset(Tvalue){获取当前Thread,从中取出ThreadLocalMapThreadtThread。currentThread();ThreadLocalMapmapgetMap(t);if(map!null){map不空则调用map的set设置当前ThreadLocal到value的映射map。set(this,value);}else{map为空则创建包含当前ThreadLocal到value映射的mapcreateMap(t,value);}}publicvoidremove(){ThreadLocalMapmgetMap(Thread。currentThread());if(m!null){从当前map中删除映射m。remove(this);}}ThreadLocalMapgetMap(Threadt){returnt。threadLocals;}voidcreateMap(Threadt,TfirstValue){t。threadLocalsnewThreadLocalMap(this,firstValue);}}
  上面的ThreadLocal的实现中大部分都是对ThreadLocalMap的操作封装,那么ThreadLocalMap是怎么实现的呢?ThreadLocalMap是ThreadLocal类的静态内部类。线性探查法的Map
  ThreadLocalMap和HashMap有所不同,ThreadLocalMap使用线性探查法而不是拉链法解决hash冲突问题。
  线性探查法可以用一个小例子来理解,想象一个停车场的场景,停车场中有一排停车位,停车时,会计算车子的hashCode算出在停车位中的序号,停上去,如果那个车位有车了,则尝试停到它的下一个车位,如果还有车则继续尝试,到末尾之后从头再来。当取车时,则按照hashCode去找车,找到对应的位置后,要看一下对应的车位上是不是自己的车,如果不是,尝试找下一个车位,如果找到了自己的车,则说明车存在,如果遇到车位为空,说明车不在。要开走车时,不光是简单开走就可以了,还得把自己车位后面的车重新修改车位,因为那些车可能因为hash冲突更换了位置,修改车位的范围是当前位置到下一个为空的车位位置。当然还有扩容的情况,后面代码里会具体介绍。
  那么为什么使用线性探测法而不是链表法呢?主要是因为数组结构更节省内存空间,并且一般ThreadLocal变量不会很多,通过0x61c88647这个黄金分割的递增hashCode也能比较好的分布在数组上减少冲突。使用WeakReference引用ThreadLocal对象
  Map中的元素用一个Entry类表示,Entry包含了对ThreadLocal的WeakReference,以及对ThreadLocal值的强引用。Map中的Entry,也是数组中的元素,会使用WeakReference引用ThreadLocal对象,value的对象是默认的强引用staticclassEntryextendsWeakReferenceThreadLocallt;?{Objectvalue;Entry(ThreadLocallt;?k,Objectv){super(k);valuev;}}
  为什么使用WeakReference对象引用呢?很多文章都提到内存泄漏,但是都没有说明白具体是什么样的内存泄漏,不少文章写道是因为value是强引用,如果线程一直存活会一直让value释放不了,这个其实并不准确,因为如果ThreadLocal字段是static的,这个static变量对象的声明周期和class对象是一致的,而class对象出现卸载的条件非常少,大部分类加载后一直存活,因此即使Entry声明成WeakReference,GC后static的ThreadLocal对象也不会被回收。
  原因究竟是为什么呢?
  这个要从要求ThreadLocal变量声明成staticfinal说起。如果不是staticfinal,比如是实例字段,则这个ThreadLocal字段可能会出现非常多个ThreadLocal实例,而不是静态常量一样一个classloader内只有一个。如果有非常多ThreadLocal实例,想想ThreadLocal的实现,是在线程内有一个Map保存ThreadLocal到value的映射。这样即使ThreadLocal实例对象已经不使用了,只要Thread对象存活,被引用的对象就无法释放。这就是使用WeakReference的原因,WeakReference引用不会影响对象被GC。这样gc后会清理掉ThreadLocalMap中已经失效的映射。也就是当我们没有正确使用ThreadLocal时(没有使用static字段),是可能出现内存泄露的,因为ThreadLocalMap中保存了对ThreadLocal的引用,ThreadLocalMap通过WeakReference以及清理机制在一定程度上缓解了这个问题。
  下面用一段代码来阐述一下。publicstaticvoidmain(String〔〕args){for(inti0;i100;i){newPeople()。say();}}staticclassPeople{staticAtomicIntegercounternewAtomicInteger();privatefinalThreadLocalStringthreadLocalThreadLocal。withInitial(()hellocounter。getAndIncrement());publicvoidsay(){System。out。println(threadLocal。get());}}
  我们创建了一个People类,并且创建了一个类型为ThreadLocal的实例字段,我们在main方法中连续调用say()方法,会发现打印出来的threadLocal的值是不一样的,虽然我们这些调用都在同一个线程中,但是因为每次调用的ThreadLocal对象是不同的,也就是ThreadLocalMap的key不相同。如果我们把ThreadLocal字段加上static,就会发现打印出来的都是相同的值了。长时间运行的线程是有可能出现的,比如tomcat的http处理线程,grpc的rpc业务处理线程等都是长时间一直运行的。
  另外因为Entry有对value的强引用,所以在线程业务处理的最后可以主动调用remove方法清理ThreadLocal,加快垃圾对象的回收,可以避免长时间存活而晋升到老年代。例如如果我们在Filter中使用ThreadLocal,一般在处理之前获取设置ThreadLocal,处理完成后,remove()删除ThreadLocal。privatestaticfinalThreadLocalStringuserNameThreadLocalnewThreadLocal();OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain){userNameThreadLocal。set(getName(request));try{filterChain。doFilter(request,response);}finally{userNameThreadLocal。remove();}}
  下面看一下ThreadLocalMap的实现,也就是线性探测法的具体实现。使用数组存放ThreadLocal到value的映射Entry,也有一个threshold(按照数组长度乘以23得到),元素数量达到threshold时,会触发resize。在ThreadLocalMap中也有清理staleEntry的处理,当ThreadLocal对象没有强引用后,Entry。get()就会返回null,这个Entry就称为staleentry,就可以触发清理工作来回收空间。
  ThreadLocalMap中定义了Entry〔〕数组,可以想象成循环数组,在线性探查中,如果遍历到边界后会从另一头继续遍历。threshold是扩容的阈值,当Map中元素数量达到threshold的时候会进行扩容,threshold通过数组长度乘以23得到,23就是Map的loadfactor(也翻译成负载因子)staticclassThreadLocalMap{初始数组的大小privatestaticfinalintINITIALCAPACITY16;存放Entry的数组,和HashMap一样是惰性创建的,并且长度是2的次方,每次扩容乘以2。privateEntry〔〕table;privateintsize0;触发resize的阈值,privateintthreshold;Defaultto0threshold是数组长度乘以23privatevoidsetThreshold(intlen){thresholdlen23;}线性探测法使用的计算后一个数组index的方法,到达数组最后之后会从0开始privatestaticintnextIndex(inti,intlen){return((i1len)?i1:0);}线性探测法使用的计算前一个数组index的方法,到达数组最前面后会总数组的最后开始privatestaticintprevIndex(inti,intlen){return((i10)?i1:len1);}}
  getEntry方法返回ThreadLocal对应的Entry,通过与操作得到非负数的数组index获取某个ThreadLocal对应的EntryprivateEntrygetEntry(ThreadLocallt;?key){通过hashCode和(数组长度1)做与操作,在数组长度为2的次方时,等价于对数组长度取余并且不会返回负数,因为数组长度1的高位都是0intikey。threadLocalHashCode(table。length1);Entryetable〔i〕;数组对应index位置的元素是当前ThreadLocal对象时,直接返回这个Entryif(e!nulle。get()key)returne;else其他情况都调用getEntryAfterMiss处理,会到后面的位置继续查找returngetEntryAfterMiss(key,i,e);}
  getEntryAfterMiss方法在ThreadLocal计算的hashCode直接找到的数组中的位置和ThreadLocal不匹配时,继续在后面的Entry查找(直接找到的Entry不为空的情况,为空直接返回null)如果遇到Entry。get返回null情况,说明这个Entry的ThreadLocal对象没有强引用了,也就是staleentry,会进行清理,也就是调用expungeStaleEntry方法。privateEntrygetEntryAfterMiss(ThreadLocallt;?key,inti,Entrye){Entry〔〕tabtable;intlentab。length;从i数组位置开始不断遍历,直到数组Entry为nullwhile(e!null){调用Entry。get获取ThreadLocal对象ThreadLocallt;?ke。get();如果是同一个对象,说明找到了,返回if(kkey)returne;如果ThreadLocal对象为null,说明被回收了,需要进行清理if(knull)expungeStaleEntry方法会负责清理,并且如果后面的元素是因为和当前i所在的ThreadLocal冲突而后移的,则会把后面的元素向左移动归位expungeStaleEntry(i);else否则,继续遍历下一个元素,到头之后从0重来inextIndex(i,len);获取下一个位置的Entry对象etab〔i〕;}returnnull;}
  set方法负责在Map中写入ThreadLocal到value的映射。privatevoidset(ThreadLocallt;?key,Objectvalue){Entry〔〕tabtable;intlentab。length;计算出indexintikey。threadLocalHashCode(len1);从index开始遍历,会不会出现满了没有地方添加的情况呢?是不会的,因为这个方法的最后有判断threshold(loadfactor23,数组初始大小为16)扩容的逻辑for(Entryetab〔i〕;e!null;etab〔inextIndex(i,len)〕){ThreadLocallt;?ke。get();找到了相同的ThreadLocal,说明Entry已经存在,进行value覆盖if(kkey){e。valuevalue;return;}如果ThreadLocal对象是null,调用replaceStaleEntry替换staleentryif(knull){replaceStaleEntry(key,value,i);return;}}说明没有找到ThreadLocal或替换staleentry,则会创建一个新的Entry放在i的位置。tab〔i〕newEntry(key,value);记录元素数量intszsize;先清理一部分数组中的staleentry,再判断下元素数量是否超过thresholdif(!cleanSomeSlots(i,sz)szthreshold)如果元素数量达到了threshold,进行rehash扩容rehash();}
  remove方法,会从Map中删除ThreadLocal对象的映射,并且也会触发expungeStaleEntry清理privatevoidremove(ThreadLocallt;?key){Entry〔〕tabtable;intlentab。length;intikey。threadLocalHashCode(len1);for(Entryetab〔i〕;e!null;etab〔inextIndex(i,len)〕){找到了TheadLocal对应的Entry后if(e。get()key){调用clear清理掉队ThreadLocal的引用,clear完之后中调用get就会返回null,相当于是staleentry了e。clear();触发清理,也会释放对value的引用expungeStaleEntry(i);return;}}}
  replaceStaleEntry会替换staleSlot上的Entry为ThreadLocal对象到value的映射privatevoidreplaceStaleEntry(ThreadLocallt;?key,Objectvalue,intstaleSlot){Entry〔〕tabtable;intlentab。length;Entrye;向前找第一个不为null的数组位置,作为这次replace中会清理的起点,避免频繁全量rehashintslotToExpungestaleSlot;for(intiprevIndex(staleSlot,len);(etab〔i〕)!null;iprevIndex(i,len)){if(e。get()null)slotToExpungei;}for(intinextIndex(staleSlot,len);(etab〔i〕)!null;inextIndex(i,len)){ThreadLocallt;?ke。get();找到了元素,进行替换if(kkey){e。valuevalue;tab〔i〕tab〔staleSlot〕;tab〔staleSlot〕e;if(slotToExpungestaleSlot)slotToExpungei;cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);return;}在上面for循环中没有找到staleentry的情况下,如果这个for循环中找到了,替换slotToExpungeif(knullslotToExpungestaleSlot)slotToExpungei;}如果没有找到原有的key,则直接替换staleSlottab〔staleSlot〕。valuenull;tab〔staleSlot〕newEntry(key,value);清理if(slotToExpunge!staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);}
  expungeStaleEntry方法从staleSlot开始清理staleentry,直到遇到一个null的entryprivateintexpungeStaleEntry(intstaleSlot){Entry〔〕tabtable;intlentab。length;先清理staleSlot位置的Entry的引用tab〔staleSlot〕。valuenull;tab〔staleSlot〕null;size;从staleSlot后面开始重新计算Entry位置,直到遇到null数组元素Entrye;inti;for(inextIndex(staleSlot,len);(etab〔i〕)!null;inextIndex(i,len)){ThreadLocallt;?ke。get();knull说明是staleentry,清理if(knull){e。valuenull;tab〔i〕null;size;}else{否则重新计算存放entry位置inthk。threadLocalHashCode(len1);if(h!i){tab〔i〕null;UnlikeKnuth6。4AlgorithmR,wemustscanuntilnullbecausemultipleentriescouldhavebeenstale。while(tab〔h〕!null)hnextIndex(h,len);tab〔h〕e;}}}returni;}
  rehash方法会先完整清理一遍,然后再check下size,如果刚才的清理后size还是大于34threshold,也就是大约12数组长度,则发起resize扩容privatevoidrehash(){expungeStaleEntries();Uselowerthresholdfordoublingtoavoidhysteresisif(sizethresholdthreshold4)resize();}
  resize扩容,会把数组长度扩大一倍,并且对Entry元素重新计算存放位置privatevoidresize(){Entry〔〕oldTabtable;intoldLenoldTab。length;计算新数组长度为老数组的两倍intnewLenoldLen2;创建新数组Entry〔〕newTabnewEntry〔newLen〕;intcount0;循环遍历老数组的元素,依次放到新数组中,相当于依次调用set方法for(Entrye:oldTab){if(e!null){ThreadLocallt;?ke。get();如果resize的时候发现staleentry,把value设置成null释放对应的引用if(knull){e。valuenull;HelptheGC}else{否则在新数组中寻找可以存放的位置,先计算hashindexinthk。threadLocalHashCode(newLen1);如果数组中对应位置有元素,则一直向后遍历while(newTab〔h〕!null)hnextIndex(h,newLen);直到一个新位置可以存放newTab〔h〕e;计数加一count;}}}数组元素转移完成,修改thresholdsetThreshold(newLen);修改sizesizecount;替换table数组tablenewTab;}
  expungeStaleEntries方法清理数组中所有的staleEntryprivatevoidexpungeStaleEntries(){Entry〔〕tabtable;intlentab。length;遍历所有的数组元素for(intj0;jlen;j){Entryetab〔j〕;如果entry不为空但是get出来的ThreadLocal为空if(e!nulle。get()null)从j位置开始清理expungeStaleEntry(j);}}}ThreadLocal的使用注意事项ThreadLocal字段应该声明成staticfinalThreadLocal在线程处理完成后建议主动remove注意跨线程使用ThreadLocal的问题ThreadLocal通过避免线程间共享数据,可以解决一些线程安全问题,并且可以跨代码区域传递参数,但是也带来了一些隐式约定,要避免滥用作参数隐式传递延伸:netty中FastThreadLocal的优化
  jdk中ThreadLocal的实现就是最好的吗?并不见得,高性能的io框架netty中就对ThreadLocal进行了优化,提供了FastThreadLocal。那么FastThreadLocal究竟Fast快在哪里呢?
  在上面ThreadLocal的实现分析中我们可以看到ThreadLocal中是有可能出现hash冲突而进行线性探测的问题的,而FastThreadLocal通过简单的方法巧妙的解决了这个问题。FastThreadLocal类中保存了一个数组中的index,这样get操作就变成了先直接拿到index再从数组中按照index读取,而不会像ThreadLoacl还可能需要向后遍历。index是通过0开始递增分配的。FastTheadLocal中增加了UNSET和initialValue的null做区分,避免ThreadLocal在get遇到initialValue()返回null时每次get()总会调用setInitialValue的问题
  FastThreadLocal要搭配FastThreadLocalThread使用,FastThreadLocalThread继承Thread并定义了一个InternalThreadLocalMap对象,和ThreadLocalMap类似,InternalThreadLocalMap也是存放当前线程的FastThreadLocal到value的映射。publicclassFastThreadLocalThreadextendsThread{privateInternalThreadLocalMapthreadLocalMap;}
  get方法实现是获取当前线程FastThreadLocalThread的InternalThreadLocalMap,然后从FastThreadLocal拿到index调用indexedVariable获取value。publicclassFastThreadLocalV{每个FastThreadLocal对象有自己的index,对应数组中的位置privatefinalintindex;publicFastThreadLocal(){indexInternalThreadLocalMap。nextVariableIndex();}publicfinalVget(){InternalThreadLocalMapthreadLocalMapInternalThreadLocalMap。get();ObjectvthreadLocalMap。indexedVariable(index);如果initialValue返回了null,则不会多次执行initialize方法if(v!InternalThreadLocalMap。UNSET){return(V)v;}returninitialize(threadLocalMap);}privateVinitialize(InternalThreadLocalMapthreadLocalMap){Vvnull;try{vinitialValue();}catch(Exceptione){PlatformDependent。throwException(e);}threadLocalMap。setIndexedVariable(index,v);addToVariablesToRemove(threadLocalMap,this);returnv;}}
  InternalThreadLocalMap的indexedVariable方法直接按照index获取数组元素publicfinalclassInternalThreadLocalMap{staticfinalAtomicIntegernextIndexnewAtomicInteger();publicstaticfinalObjectUNSETnewObject();publicstaticInternalThreadLocalMapget(){ThreadthreadThread。currentThread();returnthreadinstanceofFastThreadLocalThread?fastGet((FastThreadLocalThread)thread):slowGet();}privatestaticInternalThreadLocalMapfastGet(FastThreadLocalThreadthread){InternalThreadLocalMapthreadLocalMapthread。threadLocalMap();if(threadLocalMapnull){thread。setThreadLocalMap(threadLocalMapnewInternalThreadLocalMap());}returnthreadLocalMap;}Object〔〕indexedVariables;publicObjectindexedVariable(intindex){Object〔〕lookupindexedVariables;returnindexlookup。length?lookup〔index〕:UNSET;}}classUnpaddedInternalThreadLocalMap{}
  不过FastThreadLocal没有做内存泄漏保护,如果我们使用不正确,比如创建了大量ThreadLocal对象,则可能会出现数组内存不断增长,这就需要我们在使用时注意ThreadLocal声明成static,并且尽量在线程处理完成后主动remove
  本文转载自bytejava

触目惊心!翻翻美国油气业带血的账单乌克兰危机升级已近一年。美国推动欧洲盟友对俄罗斯实施严厉经济制裁乃至能源禁运,试图扼住俄经济命脉,其结果严重扰乱全球能源供应链。国际观察人士认为,欧洲国家高度依赖俄能源供……黑鲨5系列3月30日发布来看看这份来自太空的邀请函【手机中国新闻】3月30日晚,黑鲨即将举办新品发布会,正式推出黑鲨5系列旗舰。官方表示此次将以热爱之名,邀请玩家乘坐黑鲨空间站,一起奔赴星辰大海。黑鲨空间站今天,我……8999元,戴尔推出新款游匣G15游戏本i712700HRTIT之家2月11日消息,戴尔现已上架新款游匣G15游戏本,搭载i712700H处理器和RTX3060显卡,最高可选2K240Hz屏,2月17日正式开卖。IT之家了解到,新……戴尔上架G2422HS电竞显示器23。8英寸165Hz,14IT之家2月8日消息,戴尔G2422HS电竞显示器现已上架京东,标价1599元,首发50元定金可抵150元。IT之家了解到,这款显示器搭载了一块23。8英寸的FastIP……戴尔发布U3223QE显示器32英寸4KIPSBlack屏,IT之家2月7日消息,戴尔海外官网现已上架新款U3223QE显示器,搭载了LG最新的IPSBlack屏,售价1150美元(约7325。5元人民币)。IT之家了解到,戴尔U……世嘉宣布打造世界最快PC,时速高达100公里感谢IT之家网友肖战割割的线索投递!IT之家12月31日消息,在硬件领域,世界上速度最快的PC无疑极具噱头与挑战性。本周,世嘉宣布与英特尔、华擎等合作共同打造了一款……7299元,戴尔32英寸显示器G3223Q上架京东4K144IT之家3月20日消息,戴尔32英寸4K显示器G3223Q现已上架京东并开启预售,支持144Hz刷新率,预售价7299元,将于3月28日正式开售。IT之家了解到,戴尔G3……戴尔发布新款G2723HN显示器,27英寸FHD165HzIT之家3月19日消息,戴尔官网现已上架新款G2723HN显示器,27英寸FHD165Hz,官网标价2999元。IT之家了解到,这款显示器采用了27英寸FASTIPS屏,……天问一号助力科学家研究火星日凌获重要成果火星日凌是指地球、火星运行至太阳两侧且三者近乎处于一条直线的自然现象。2021年9月下旬至10月中旬,执行我国首次火星探测任务的天问一号经历了首次火星日凌,与地球的通信受到太阳……优派发布新款便携显示器17。2英寸大屏,1080p144HzIT之家11月30日消息,据TechPowerUp消息,优派今天推出VX1755便携显示器,17。2英寸,1080p144Hz规格。据介绍,优派VX1755便携显示器的原……1999元起,小米11青春活力版今日开售搭载骁龙778G,新IT之家12月10日消息,小米11青春活力版手机于昨日发布,搭载骁龙778G处理器,售价1999元起,将于今日上午10点正式开售。8GB128GB版本售价1999元……1999元起,小米11青春活力版正式发布史上最轻薄的小米5GIT之家12月9日消息,今日上午,小米正式发布了小米11青春活力版手机,售价1999元起,将于明日(12月10日)上午10点正式开售。8GB128GB版本售价1999元……
OPPOReno6标准版12256GB版立减200元90HzIT之家9月29日消息OPPO于5月27日发布了Reno6系列手机。十一国庆即将到来之际,OPPOReno6标准版又迎来新一轮调价优惠政策。8GB128GB版降价100元……6499元,OPPOFindX3Pro摄影师版正式开售支持长IT之家9月22日消息今日,OPPOFindX3Pro摄影师版正式开售,配备16GB内存与512GB机身存储,支持IP68级别防水防尘,售价6499元。设计方面,OPPO……小米神秘新机现身地铁搭载居中挖孔屏,有望为下一代旗舰小米12IT之家10月31日消息,又到了喜闻乐见的地铁发布会环节,小米一款工程样机近日在微博被曝光。从图中可以看到,该手机套上了防止泄密的保护壳,侧边有小米标志(还是以前的标志)……vivo新款智能手表即将到来,现已通过工信部认证和蓝牙认证IT之家10月10日消息有网友发现vivo新款智能手表已通过蓝牙SIG认证,而该机此前已通过国家工信部认证。据悉,该机支持蓝牙5。1规范,支持4G网络,支持独立eUICC……一文看懂vivo发展史从步步高通讯科技业务到国内出货量第一1989年,小霸王学习机的名号响彻大江南北,无数的孩子求着家长在家里购置一台用以学习,虽然很多家长心知肚明,这台所谓的学习机最后的用途就是玩一些FC游戏,但架不住软磨硬泡,最终……联想小新PadPro12。6双模磁吸键盘官宣大面积触控板类WIT之家10月21日消息,今日,联想小新官方公布了小新PadPro12。6双模磁吸键盘。据介绍,这款键盘支持磁吸蓝牙双模连接,拥有1。3mm键程和微笑型贴合键帽,配备大面……恭喜穆帅!2大强援加盟在即,打造新阵型,搅乱意甲格局冲击冠军夏窗开启,许多球队都迎来了明显的补强,巴萨,曼城,拜仁和阿森纳等豪门都是花费了上亿资金进行补强球队阵容,此外热刺,国米,尤文也都是针对球队的弱点引进新的球员。反观穆里尼奥挂帅的……华为新款Matebook14s曝光通过EVO认证,还有雷电4感谢IT之家网友肖战割割的线索投递!IT之家10月3日消息今年9月13日,华为推出两款笔记本电脑新品MateBook13s和14s,均采用触控屏,搭载华为移动应用,将PC……华为申请VPencil商标,有望用于MateV折叠屏手机IT之家10月7日消息据LetsGoDigital报道,10月4日,华为技术有限公司向欧盟知识产权局(EUIPO)申请了HuaweiVPencil商标。图自LetsGoD……孙亚芳给任正非说了三点问题,任正非看后大为赞赏,接班人是关键孙亚芳是华为公司前董事长,她和任正非的组合被称之为左非右芳,外界称其为华为女王。在华为创业早期,孙亚芳加入华为公司,先后从事过市场、人力资源、战略管理和培训等工作,199……华为MateBook16即将全球发布搭载锐龙5000系列处理IT之家10月5日消息据TheVerge报道,华为MateBook16笔记本电脑将在全球发布,搭载锐龙5000系列处理器,R5机型起售价为1099欧元(约8231。51元人民币……送100元话费网易云音乐黑胶VIP年卡168元大促网易云音乐黑胶VIP年卡大促,日常售价158元(原价216元),限时特价168元送100元话费,年卡相当于68元、折合3。1折优惠。活动时间:9月28日10月8日(不定时……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网