最新要闻
- 环球聚焦:曝吴秀波儿子家暴成性,女方晒照满嘴是血,指证男方出轨乱约
- 周杰伦演唱会数百万人疯抢!演唱会门票30秒卖光
- 环球新资讯:长安发律师函控诉银河之光抄袭 吉利回应:毫无根据、误导公众
- 全白外观拥有硬核实力!影驰Z790金属大师D5 Wi-Fi白金版主板评测:内存轻松提速10%
- 环球快讯:越来越多PC用户不愿买显卡了:2022年Q4桌面独显销量暴跌43%
- 世界第一次!中国空间站看到了在轨航天员的“三维皮肤”
- 【时快讯】89元 小米无线键鼠套装2上架:鼠标轻至45g
- 环球精选!运酒撞破酒缸1分钟损失5万!男子:一坛酒1000斤 1斤50块钱
- IPO参考:联域光电拟深市主板IPO 郝氏控股二次递表港交所
- 【全球快播报】感冒的食疗偏方秘方_感冒的食疗偏方
- 8尺夫人面对面!《生化危机8 VR》IGN 8分 MTC用户9.7分
- 环球热点评!40多万的车没有语音控制功能!宝马遭X3车主集体投诉
- 今日聚焦!一加Ace 2V搭载旗舰级2.8D微弧玻璃:手感温润如玉
- 世界讯息:续航1整年!小米智能门锁M20大屏猫眼版开售:首发1899元
- 世界要闻:《艾尔登法环》原子之心机械姐妹花Mod 性感吸睛
- 自得其乐是什么意思?自得其乐的名人例子有哪些?
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
环球即时:Synchronized,我要一层一层剥开你的心
三种应用方式
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象。
修饰实例方法
所谓的实例对象锁就是用synchronized修饰实例对象中的实例方法,注意是实例方法不包括静态方法,如下
COPYpublic class AccountingSync implements Runnable{ //共享资源(临界资源) static int i=0; /** * synchronized 修饰实例方法 */ public synchronized void increase(){ i++; } @Override public void run() { for(int j=0;j<1000000;j++){ increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync instance=new AccountingSync(); Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } /** * 输出结果: * 2000000 */}
上述代码中,我们开启两个线程操作同一个共享资源即变量i,由于i++;操作并不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分两步完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败,因此对于increase方法必须使用synchronized修饰,以便保证线程安全。此时我们应该注意到synchronized修饰的是实例方法increase,在这样的情况下,当前线程的锁便是实例对象instance,注意Java中的线程同步锁可以是任意对象。从代码执行结果来看确实是正确的,倘若我们没有使用synchronized关键字,其最终输出结果就很可能小于2000000,这便是synchronized关键字的作用。这里我们还需要意识到,当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法,当然如果是一个线程 A 需要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另一个线程 B 需要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是允许的,因为两个实例对象锁并不同相同,此时如果两个线程操作数据并非共享的,线程安全是有保障的,遗憾的是如果两个线程操作的是共享数据,那么线程安全就有可能无法保证了,如下代码将演示出该现象
(资料图)
COPYpublic class AccountingSyncBad implements Runnable{ static int i=0; public synchronized void increase(){ i++; } @Override public void run() { for(int j=0;j<1000000;j++){ increase(); } } public static void main(String[] args) throws InterruptedException { //new新实例 Thread t1=new Thread(new AccountingSyncBad()); //new新实例 Thread t2=new Thread(new AccountingSyncBad()); t1.start(); t2.start(); //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回 t1.join(); t2.join(); System.out.println(i); }}
上述代码与前面不同的是我们同时创建了两个新实例AccountingSyncBad,然后启动两个不同的线程对共享变量i进行操作,但很遗憾操作结果是1452317而不是期望结果2000000,因为上述代码犯了严重的错误,虽然我们使用synchronized修饰了increase方法,但却new了两个不同的实例对象,这也就意味着存在着两个不同的实例对象锁,因此t1和t2都会进入各自的对象锁,也就是说t1和t2线程使用的是不同的锁,因此线程安全是无法保证的。解决这种困境的的方式是将synchronized作用于静态的increase方法,这样的话,对象锁就当前类对象,由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的。下面我们看看如何使用将synchronized作用于静态的increase方法。
修饰静态方法
当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态 成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁,看如下代码
COPYpublic class AccountingSyncClass implements Runnable{ static int i=0; /** * 作用于静态方法,锁是当前class对象,也就是 * AccountingSyncClass类对应的class对象 */ public static synchronized void increase(){ i++; } /** * 非静态,访问时锁不一样不会发生互斥 */ public synchronized void increase4Obj(){ i++; } @Override public void run() { for(int j=0;j<1000000;j++){ increase(); } } public static void main(String[] args) throws InterruptedException { //new新实例 Thread t1=new Thread(new AccountingSyncClass()); //new心事了 Thread t2=new Thread(new AccountingSyncClass()); //启动线程 t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); }}
由于synchronized关键字修饰的是静态increase方法,与修饰实例方法不同的是,其锁对象是当前类的class对象。注意代码中的increase4Obj方法是实例方法,其对象锁是当前实例对象,如果别的线程调用该方法,将不会产生互斥现象,毕竟锁对象不同,但我们应该意识到这种情况下可能会发现线程安全问题(操作了共享静态变量i)。
修饰代码块
除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了,同步代码块的使用示例如下:
COPYpublic class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync(); static int i=0; @Override public void run() { //省略其他耗时操作.... //使用同步代码块对变量i进行同步操作,锁对象为instance synchronized(instance){ for(int j=0;j<1000000;j++){ i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); }}
从代码看出,将synchronized作用于一个给定的实例对象instance,即当前实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有instance实例对象锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待,这样也就保证了每次只有一个线程执行i++;操作。当然除了instance作为对象外,我们还可以使用this对象(代表当前实例)或者当前类的class对象作为锁,如下代码:
COPY//this,当前实例对象锁synchronized(this){ for(int j=0;j<1000000;j++){ i++; }}//class对象锁synchronized(AccountingSync.class){ for(int j=0;j<1000000;j++){ i++; }}
了解完synchronized的基本含义及其使用方式后,下面我们将进一步深入理解synchronized的底层实现原理。
特性
原子性
被 synchronized 修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。因此,在 Java 中可以使用 synchronized 来保证方法和代码块内的操作是原子性的。
可见性
对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。
有序性
synchronized 本身是无法禁止指令重排和处理器优化的,
as-if-serial 语义:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。
编译器和处理器无论如何优化,都必须遵守 as-if-serial 语义。
synchronized 修饰的代码,同一时间只能被同一线程执行。所以,可以保证其有序性。
可重入性
从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功,在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。
原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
字节码指令
synchronized同步块使用了monitorenter和monitorexit指令实现同步,这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的所有权。
锁的释放
获取建立的 happens before 关系
锁是 java 并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
下面是锁释放 - 获取的示例代码:
COPYclass MonitorExample { int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 …… } //6}
假设线程 A 执行 writer() 方法,随后线程 B 执行 reader() 方法。根据 happens before 规则,这个过程包含的 happens before 关系可以分为两类:
- 根据程序次序规则,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。
- 根据监视器锁规则,3 happens before 4。
- 根据 happens before 的传递性,2 happens before 5。
上述 happens before 关系的图形化表现形式如下:
在上图中,每一个箭头链接的两个节点,代表了一个 happens before 关系。黑色箭头表示程序顺序规则;橙色箭头表示监视器锁规则;蓝色箭头表示组合这些规则后提供的 happens before 保证。
上图表示在线程 A 释放了锁之后,随后线程 B 获取同一个锁。在上图中,2 happens before 5。因此,线程 A 在释放锁之前所有可见的共享变量,在线程 B 获取同一个锁之后,将立刻变得对 B 线程可见。
内存布局
在Hotspot虚拟机中,对象在内存中的布局分为三块区域:
- 对象头(Mark Word、Class Metadata Address)、
- 实例数据
- 对齐填充
实例数据
存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
对齐填充
由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这点了解即可。
对象头
Java对象头是实现synchronized的锁对象的基础。一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。
它实现synchronized的锁对象的基础,这点我们重点分析它,一般而言,synchronized使用的锁对象是存储在Java对象头里的,jvm中采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark Word 和 Class Metadata Address 组成,其结构说明如下表:
虚拟机位数 | 头对象结构 | 说明 |
---|---|---|
32/64bit | Mark Word | 存储对象的hashCode、锁信息或分代年龄或GC标志等信息 |
32/64bit | Class Metadata Address | 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例 |
Mark Word
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit)。
锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit 锁标志位 |
---|---|---|---|---|
无锁状态 | 对象HashCode | 对象分代年龄 | 0 | 01 |
由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下,除了上述列出的Mark Word默认存储结构外,还有如下可能变化的结构:
其中轻量级锁和偏向锁是Java 6 对 synchronized 锁进行优化后新增加的,稍后我们会简要分析。这里我们主要分析一下重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)
COPYObjectMonitor() { _header = NULL; _count = 0; //记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示
由此看来,monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因(关于这点稍后还会进行分析),ok~,有了上述知识基础后,下面我们将进一步分析synchronized在字节码层面的具体语义实现。
Class Metadata Address
类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
Array length
如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。
底层原理
代码块底层原理
关于synchionized代码块底层原理和synchionized方法底层原理,这里通过利用javap直观的展现加了synchionized后,我们的代码到底出现了些什么指令来观察
反编译代码
现在我们重新定义一个synchronized修饰的同步代码块,在代码块中操作共享变量i,如下:
COPYpublic class SyncCodeBlock { public int i; public void syncTask(){ //同步代码块 synchronized (this){ i++; } }}
编译上述代码并使用javap反编译后得到字节码如下(这里我们省略一部分没有必要的信息):
COPYClassfile /***/src/main/java/com/zejian/concurrencys/SyncCodeBlock.class Last modified 2018-07-25; size 426 bytes MD5 checksum c80bc322c87b312de760942820b4fed5 Compiled from "SyncCodeBlock.java"public class com.hc.concurrencys.SyncCodeBlock minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: //........省略常量池中数据 //构造函数 public com.hc.concurrencys.SyncCodeBlock(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 7: 0 //===========主要看看syncTask方法实现================ public void syncTask(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter //注意此处,进入同步方法 4: aload_0 5: dup 6: getfield #2 // Field i:I 9: iconst_1 10: iadd 11: putfield #2 // Field i:I 14: aload_1 15: monitorexit //注意此处,退出同步方法 16: goto 24 19: astore_2 20: aload_1 21: monitorexit //注意此处,退出同步方法 22: aload_2 23: athrow 24: return Exception table: //省略其他字节码.......}SourceFile: "SyncCodeBlock.java"
我们主要关注字节码中的如下代码
COPY3: monitorenter //进入同步方法//..........省略其他 15: monitorexit //退出同步方法16: goto 24//省略其他.......21: monitorexit //退出同步方法
锁的竞争模拟
通过反编译解读–同步代码块多线程下关于锁的竞争模拟
- 首先从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置.
- 当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。
- 如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。
- 倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。
- 值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。
方法底层原理
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。
JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词),然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。
反编译代码
下面我们看看字节码层面如何实现:
COPYpublic class SyncMethod { public int i; public synchronized void syncTask(){ i++; }}
使用javap反编译后的字节码如下:
COPYClassfile /***/src/main/java/com/zejian/concurrencys/SyncMethod.class Last modified 2017-6-2; size 308 bytes MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94 Compiled from "SyncMethod.java"public class com.hc.concurrencys.SyncMethod minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool; //省略没必要的字节码 //==================syncTask方法====================== public synchronized void syncTask(); descriptor: ()V //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法 flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field i:I 5: iconst_1 6: iadd 7: putfield #2 // Field i:I 10: return LineNumberTable: line 12: 0 line 13: 10}SourceFile: "SyncMethod.java"
从字节码中可以看出,synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
这便是synchronized锁在同步代码块和同步方法上实现的基本原理的区别。同时我们还必须注意到的是在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。
本文由
传智教育博学谷狂野架构师
教研团队发布。如果本文对您有帮助,欢迎
关注
和点赞
;如果您有任何建议也可留言评论
或私信
,您的支持是我坚持创作的动力。转载请注明出处!
环球即时:Synchronized,我要一层一层剥开你的心
全球视点!轻松玩转makefile|基础知识
环球聚焦:曝吴秀波儿子家暴成性,女方晒照满嘴是血,指证男方出轨乱约
周杰伦演唱会数百万人疯抢!演唱会门票30秒卖光
环球新资讯:长安发律师函控诉银河之光抄袭 吉利回应:毫无根据、误导公众
环球微速讯:【同步、共享和内容协作软件】上海道宁与ownCloud让您的团队随时随地在任何设备上轻松处理数据
全白外观拥有硬核实力!影驰Z790金属大师D5 Wi-Fi白金版主板评测:内存轻松提速10%
环球快讯:越来越多PC用户不愿买显卡了:2022年Q4桌面独显销量暴跌43%
世界第一次!中国空间站看到了在轨航天员的“三维皮肤”
【时快讯】89元 小米无线键鼠套装2上架:鼠标轻至45g
环球精选!运酒撞破酒缸1分钟损失5万!男子:一坛酒1000斤 1斤50块钱
天天快看点丨易基因|独家分享:高通量测序后的下游实验验证方法——DNA甲基化篇
IPO参考:联域光电拟深市主板IPO 郝氏控股二次递表港交所
【全球快播报】感冒的食疗偏方秘方_感冒的食疗偏方
8尺夫人面对面!《生化危机8 VR》IGN 8分 MTC用户9.7分
环球热点评!40多万的车没有语音控制功能!宝马遭X3车主集体投诉
今日聚焦!一加Ace 2V搭载旗舰级2.8D微弧玻璃:手感温润如玉
世界讯息:续航1整年!小米智能门锁M20大屏猫眼版开售:首发1899元
世界要闻:《艾尔登法环》原子之心机械姐妹花Mod 性感吸睛
自得其乐是什么意思?自得其乐的名人例子有哪些?
12%是零点几?12%公积金是什么档次?
给据邮件是什么意思?给据邮件如何填写?
男主角性格冰冷的小说有哪些?男主角性格冰冷的小说推荐
兔子能喝牛奶吗?兔子吃的食物有哪些?
小米手机屏幕校准在哪里?小米手机屏幕有多大?
集成显卡可以玩英雄联盟吗?集成显卡和独立显卡有什么区别?
华为mate9哪一年生产的?华为mate9参数配置
可视化图表之奥妙——百分比堆积条形图
看热讯:基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v6.2版已发布
常用的xpath
环球观察:mybatis plus映射postgreSQL数组
世界资讯:redis实现用户查询次数限制
军用三防手机有哪些?军用三防手机推荐
苏泊尔电磁炉滴滴响不加热是什么原因?苏泊尔电磁炉滴滴响不加热怎么解决?
最新资讯:完美全面屏!努比亚Z50 Ultra官宣3月7日发布
新消息丨控告抄袭!长安给吉利发去律师函
【环球新要闻】BBA压力来了!李想:今年理想将挑战30-50万豪华SUV市场20%份额
环球快消息!SRC挖掘之Access验证校验的漏洞挖掘
【世界热闻】tomcat源码分析(一)如何启动服务
协同办公领域未来三大趋势(协同办公带来的机遇)
彻底理解 HashMap 及 LinkedHashMap,面试官请随便问!
当前焦点!轻量级CI/CD发布部署环境搭建及使用_02_docker安装jenkins
世界微速讯:理想汽车2022年亏损额创新高达20.32亿元
天天热议:腾势D9首创双枪充电 赵长江:比高压充电更节省社会成本
当前视讯!DXO排名全球第一!京东方认领荣耀Magic5/Pro屏幕
【全球播资讯】史诗级更新!雷军:小米12S Ultra 2倍变焦快捷按键正式上线
环球时讯:巧妙利用“慧言”机器人在安全场景中实践
每日消息!iOS封闭垄断 俄罗斯怒罚苹果9.06亿卢布:后者服软已悄悄支付
李想:比亚迪汽车毛利率20%左右 跟理想汽车很接近 良心
世界最新:梅西第七次当选世界足球先生 FIFA年度最佳球员!网友泪目:实至名归
全球消息!游戏第一神U上新!AMD锐龙9 7950X3D超频后更强:性能完秒13900KS
天天热点!40吨重 我国长征九号重型火箭未来将发射大型天文望远镜:探测地外生命
湖人官方:詹姆斯明日因右脚伤势缺战灰熊
全球热消息:世界第二大水电站16台机组全部通过验收:自主研制 登顶世界水电
全球观速讯丨增程是落后技术?李想:理想汽车单车型研发投入中国行业最高
环球今热点:10多天还没到 市民吐槽快递延误 客服:你投诉去吧
读Java性能权威指南(第2版)笔记04_ Java SE API技巧下
天天速递!承诺员工1人1套房!董明珠被传与王自如恋爱 格力愤怒已报案
事与愿违下一句啥意思_事与愿违下一句是什么
天天观热点:一元二次方程
全球报道:k8s中的PV和PVC
观察:Canvas画布
印尼16月大男婴重达27公斤:比8岁男童还重
焦点日报:最后一天赶紧换!苹果iPhone等设备全系电池明天涨价:最贵519元
关注:茶颜悦色回应满杯奶茶放久了变半杯:奶沫放久了会自己消掉
每日看点!小米Wi-Fi 7路由器BE7000亮相:4个2.5G口 定价千元买吗?
当前视点!3名《炉石传说》电竞运动员注册成功:然而暴雪禁止中国玩家参加炉石赛事
环球聚焦:东川区开展校园周边环境专项集中整治行动
资讯推荐:冒险岛辅助2023_冒险岛多趣辅助
记录写了6年代码的心得
今亮点!Django uwsgi问题解析
全球新消息丨不坑盒子:word/wps最强辅助工具2023最新版
天天热议:轻松超越i9-13900KS!AMD锐龙9 7950X3D首发评测:一半功耗、价格更低
【天天报资讯】电池容量最大的旗舰手机!荣耀Magic5 Pro图赏
天天速递!女生奶茶中喝出3个标签后细菌感染:商家赔付1000元及检查费用
天天视讯!做一个右键菜单 Vue 组件
天天热文:设计模式(十二)----结构型模式之代理模式和装饰者模式的区别
每日动态!闻着臭、吃着香?游客热衷打包柳州螺蛳粉 机场火车站回应
天天热点评!番茄涨价太狠 英国餐馆推出白披萨:网友吐槽太苦了
每日热文:荣耀Magic5 Pro全球首发硅碳负极电池:5450mAh超大容量
焦点观察:IDEA2022.3永久激活破解 最新激活 一键激活 亲测可用!
浅谈: sizeof()与指针
今亮点!0x07_自制操作系统My-OS实现键盘输出字符到屏幕
当前热议!Python中logging模块用法
世界滚动:C语言程序设计
天天最资讯丨有信免费电话使用攻略_有信怎么使用 免费
七夕 我们分手
微资讯!自带散热泵!一加推出45W液冷散热背夹:最高可降20℃
倒计时两分钟!SpaceX载人火箭发射最后关头被紧急叫停
世界资讯:七彩虹战戟CN700 1TB SSD评测:国产方案7.1GB/s读取、499元最香
GTX 750 Ti挑战大热单机:画面不忍直视
【热闻】“狗”气越来越足!第二代哈弗大狗正式上市:13.58万元起
【天天新视野】关于Ajax
路飞项目封装logger、response以及全局异常
最新消息:不怕被坑了!工信部出手:自动续费前5日应以显著方式提醒
1999元 极米Play 2投影仪上架:120寸大屏、Type-C供电
【世界时快讯】3D大颗粒 杰士邦特惠装30支:19.9元秒杀
异形方向盘有隐患吗?汽车博主:既不方便也不安全
从银行数字化转型来聊一聊,火山引擎 VeDI 旗下 ByteHouse 的应用场景
今日热讯:第123篇: JS函数属性与方法