热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java修改内存变量_Java内存模型与volatile关键字

1Java内存模型(JMM)的意义Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,规定线程如何,何时能看到其他线程修改过的共

1 Java内存模型(JMM)的意义

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,规定

线程如何,何时能看到其他线程修改过的共享变量的值

在必要时如何同步地访问共享变量

以实现让Java程序在各种平台下都能达到一致性的内存访问效果。

2 主内存与工作内存

Java内存模型的主要目标是定义各个变量的访问规则

即在虚拟机中将变量存储到内存和从内存中取出变量值这样的底层细节

此处的变量包括了实例域,静态域和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不存在竞争

为了获得比较好的执行效率,JMM并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码执行顺序这类权限。

JMM规定

所有的变量都存储在主内存(Main Memory)

每条线程有自己的工作内存(Working Memory)

保存了该线程使用到的变量的主内存副本拷贝(线程所访问对象的引用或者对象中某个在线程访问到的字段,不会是整个对象的拷贝!)

线程对变量的所有操作(读,赋值等)都必须在工作内存中进行,不能直接读写主内存中的变量

volatile变量依然有工作内存的拷贝,只是他特殊的操作顺序性规定,看起来如同直接在主内存读写

不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均要通过主内存

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

JVM模型与JMM不是同一层次的内存划分,基本是没有关系的,硬要对应起来,从变量,内存,工作内存的定义来看

主内存 === Java堆中的对象实例数据部分

工作内存 === 虚拟机栈中的部分区域

从更底层的层次来说

主内存直接对应于物理硬件的内存

为了更好的运行速度,虚拟机(甚至硬件系统的本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存器中,因为程序运行时主要访问读写的是工作内存

3 内存间同步操作

一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节

JMM定义了以下8种操作来完成,都具备原子性

lock(锁定)

作用于主内存变量,把一个变量标识为一条线程独占的状态

unlock(解锁)

作用于主内存变量,把一个处于锁定状态的变量释放,释放后的变量才可以被其它线程锁定

unlock之前必须将变量值同步回主内存

read(读取)

作用于主内存变量,把一个变量的值从主内存传输到工作内存,以便随后的load

load(载入)

作用于工作内存变量,把read从主内存中得到的变量值放入工作内存的变量副本

use(使用)

作用于工作内存变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到的变量的值得字节码指令时将会执行这个操作

assign(赋值)

作用于工作内存变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作

store(存储)

作用于工作内存变量,把工作内存中一个变量的值传送到主内存,以便随后的write操作使用

write(写入)

作用于主内存变量,把store操作从工作内存中得到的值放入主内存的变量中

AAffA0nNPuCLAAAAAElFTkSuQmCC

把一个变量从主内存复制到工作内存

就要顺序执行read和load

把变量从工作内存同步回主内存

就要顺序地执行store和write操作

JMM只要求上述两个操作必须按序执行,而没有保证连续执行

也就是说read/load之间、store/write之间可以插入其它指令

如对主内存中的变量a,b访问时,一种可能出现的顺序是read a->readb->loadb->load a

JMM规定执行上述八种基础操作时必须满足如下

3.1 同步规则

不允许read/load、store/write操作之一单独出现

不允许一个变量从主内存读取了但工作内存不接收,或从工作内存发起回写但主内存不接收

不允许一个线程丢弃它的最近的assign

即变量在工作内存中改变(为工作内存变量赋值)后必须把该变化同步回主内存

新变量只能在主内存“诞生”,不允许在工作内存直接使用一个未被初始化(load或assign)的变量

换话说就是一个变量在实施use,store之前,必须先执行过assign和load

如果一个变量事先没有被load锁定,则不允许对它执行unlock,也不允许去unlock一个被其它线程锁定的变量

对一个变量执行unloack前,必须把此变量同步回主内存中(执行store,write)

volatile变量可以被看作是一种 "轻量的 synchronized

可以说是JVM提供的最轻量级的同步机制

当一个变量定义为volatile后

保证此变量对所有线程的可见性

4 原子性(Atomicity)

一次只允许一个线程持有某锁,一次只有一个线程能使用共享数据

由JMM直接保证的原子性变量操作包括read、load、use、assign、store和write六个,大致可以认为基础数据类型的访问读写是原子性的

如果应用场景需要一个更大范围的原子性保证,JMM还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock与unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐匿地使用这两个操作,这两个字节码指令反映到Java代码中就是同步块synchronized关键字,因此在synchronized块之间的操作也具备原子性

5 可见性(Visibility)

当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改

JMM通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性,无论是普通变量还是volatile变量都是如此

普通变量与volatile变量的区别是

volatile的特殊规则保证了新值能立即同步到主内存,以及每使用前立即从内存刷新

因此volatile保证了线程操作时变量的可见性,而普通变量则不能保证

除了volatile,Java还有两个关键字能实现可见性

synchronized

由“对一个变量执行unlock前,必须先把此变量同步回主内存中(执行store和write)”这条规则获得的

final

被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见final字段的值

必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程可见,对域中的值做赋值和返回的操作通常是原子性的,但递增/减并不是

volatile对所有线程是立即可见的,对volatile变量所有的写操作都能立即返回到其它线程之中,换句话说,volatile变量在各个线程中是一致的,但并非基于volatile变量的运算在并发下是安全的

volatile变量在各线程的工作内存中不存在一致性问题(在各个线程的工作内存中volatile变量也可以存在不一致,但由于

每次使用之前都要先刷新 ,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),但Java里的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的

public class Atomicity {

int i;

void f(){

i++;

}

void g(){

i += 3;

}

}

编译后文件

void f();

0 aload_0 [this]

1 dup

2 getfield concurrency.Atomicity.i : int [17]

5 iconst_1

6 iadd

7 putfield concurrency.Atomicity.i : int [17]

// Method descriptor #8 ()V

// Stack: 3, Locals: 1

void g();

0 aload_0 [this]

1 dup

2 getfield concurrency.Atomicity.i : int [17]

5 iconst_3

6 iadd

7 putfield concurrency.Atomicity.i : int [17]

}

每个操作都产生了一个 get 和 put ,之间还有一些其他的指令

因此在获取和修改之间,另一个线程可能会修改这个域

所以,这些操作不是原子性的

再看下面这个例子是否符合上面的描述

public class AtomicityTest implements Runnable {

private int i = 0;

public int getValue() {

return i;

}

private synchronized void evenIncrement() {

i++;

i++;

}

public void run() {

while(true)

evenIncrement();

}

public static void main(String[] args) {

ExecutorService exec = Executors.newCachedThreadPool();

AtomicityTest at = new AtomicityTest();

exec.execute(at);

while(true) {

int val = at.getValue();

if(val % 2 != 0) {

System.out.println(val);

System.exit(0);

}

}

}

}

output:

1

该程序将找到奇数值并终止

尽管return i原子性,但缺少同步使得其数值可以在处于不稳定的中间状态时被读取

由于 i 不是 volatile ,存在可视性问题

getValue() 和 evenIncrement() 必须synchronized

对于基本类型的读/写操作被认为是安全的原子性操作

但当对象处于不稳定状态时,仍旧很有可能使用原子性操作来访问他们

最明智的做法是遵循同步的规则

volatile 变量只保证可见性

在不符合以下条件规则的运算场景中,仍需要通过加锁(使用synchronized或JUC中的原子类)来保证原子性

运算结果不依赖变量的当前值,或者能确保只有单一的线程修改变量的值

变量不需要与其它的状态变量共同参与不可变类约束

基本上,若一个域可能会被多个任务同时访问or这些任务中至少有一个是写任务,那就该将此域设为volatile

当一个域定义为 volatile 后,将具备

1.保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,其它线程每次使用前立即从主内存刷新

但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成

2.禁止指令重排序。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)

这些操作的目的是用线程中的局部变量维护对该域的精确同步

6 指令重排序

有序性:即程序执行的顺序按照代码的先后顺序执行

使用volatile变量的第二个语义是禁止指令重排序优化

普通变量仅保证该方法执行过程所有依赖赋值结果的地方能获取到正确结果,而不保证变量赋值操作的顺序与代码执行顺序一致

因为在一个线程的方法执行过程中无法感知到这一点,这也就是JMM中描述的所谓的

线程内表现为串行的语义(Within-Thread As-If-Serial Sematics)

Map configOptions;

char[] configText;

//此变量必须定义为volatile

volatile boolean initialized = false;

//假设以下代码在线程A中执行

//模拟读取配置信息,当读取完成后

//将initialized设置为true来通知其它线程配置可用

configOptions = new HashMap();

configText = readConfigFile(fileName);

processConfigOptions(configText, configOptions);

initialized = true;

//假设以下代码在线程B中执行

//等线程A待initialized为true,代表线程A已经把配置信息初始化完成

while(!initialized) {

sleep();

}

//使用线程A中初始化好的配置信息

doSomethingWithConfig();

如果定义initialized时没有使用volatile,就可能会由于指令重排序优化,导致位于线程A中最后一行的代码initialized = true被提前执行,这样在线程B中使用配置信息的代码就可能出现错误,而volatile关键字则可以完美避免

volatile变量读操作性能消耗与普通变量几乎无差,但写操作则可能会稍慢,因为它需要在代码中插入许多内存屏障指令来保证处理器不发生乱序执行

不过即便如此,大多数场景下volatile的总开销仍然要比锁小,我们在volatile与锁之中选择的唯一依据仅仅是volatile的语义能否满足使用场景的需求

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

volatile修饰的变量,赋值后(前面mov %eax,0x150 (%esi) 这句便是赋值操作) 多执行了一个1ock add1 $ 0x0,(%esp),这相当于一个内存屏障(Memory Barrier/Fence,指重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU 访问内存时,并不需要内存屏障

但如果有两个或更多CPU 访问同一块内存,且其中有一个在观测另一个,就需要内存屏障来保证一致性了

这句指令中的add1 $0x0, (%esp)(把ESP 寄存器的值加0) 显然是一个空操作(采用这个空操作而不是空操作指令nop 是因为IA32手册规定lock前缀不允许配合nop 指令使用),关键在于lock 前缀,查询IA32 手册,它的作用是使得本CPU 的Cache写入内存,该写入动作也会引起别的CPU 或者别的内核无效化(Inivalidate) 其Cache,这种操作相当于对Cache 中的变量做了一次store和write。所以通过这样一个空操作,可让前面volatile 变量的修改对其他CPU 立即可见。

那为何说它禁止指令重排序呢?

硬件架构上,指令重排序指CPU 采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。但并不是说指令任意重排,CPU需要能正确处理指令依赖情况以保障程序能得出正确的执行结果

譬如指令1把地址A中的值加10,指令2把地址A 中的值乘以2,指令3把地址B 中的值减去了,这时指令1和指令2是有依赖的,它们之间的顺序不能重排,(A+10) 2 与A2+10显然不等,但指令3 可以重排到指令i、2之前或者中间,只要保证CPU 执行后面依赖到A、B值的操作时能获取到正确的A 和B 值即可。所以在本CPU 中,重排序看起来依然是有序的。因此lock add1 $0x0,(%esp) 指令把修改同步到内存时,意味着所有之前的操作都已经执行完成,这样便形成了“指令重排序无法越过内存屏障”的效果

举个例子

int i = 0;

boolean flag = false;

i = 1; //语句1

flag = true; //语句2

从代码顺序上看,语句1在2前,JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)

比如上面的代码中,语句1/2谁先执行对最终的程序结果并无影响,就有可能在执行过程中,语句2先执行而1后虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,靠什么保证?数据依赖性

编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序

举例

double pi = 3.14; //A

double r = 1.0; //B

double area = pi * r * r; //C

AAffA0nNPuCLAAAAAElFTkSuQmCC

A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。

因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。

但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序

AAffA0nNPuCLAAAAAElFTkSuQmCC

这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果

但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。这是就需要内存屏障来保证可见性了

回头看一下JMM对volatile 变量定义的特殊规则

假定T 表示一个线程,V 和W 分别表示两个volatile变量,那么在进行read, load, use,assign,store,write时需要满定如下规则

只有当线程T 对变量V 执行的前一个动作是load ,线程T 方能对变量V 执行use;并且,只有当线程T 对变量V 执行的后一个动作是use,线程T才能对变量V执行load.线程T 对变量V 的use可认为是和线程T对变量V的load,read相关联,必须连续一起出现(这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值语,用于保证能看见其他线程对变量V所做的修改后的值)

只有当线程T 对变量V 执行的前一个动作是 assign ,线程T才能对变量V 执行store

并且,只有当线程T对变量V执行的后一个动作是store ,线程T才能对变量V执行assign

线程T对变量V的assign可以认为是和线程T对变量V的store,write相关联,必须连续一起出现(这条规则要求在工作内存中,每次修改V 后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改)

假定动作A 是线程T 对变量V实施的use或assign,假定动作F 是和动作A 相关联的load或store,假定动作P 是和动作F 相应的对变量V 的read 或write

类似的,假定动作B 是线程T 对变量W 实施的use或assign 动作,假定动作G是和动作B 相关联的load或store,假定动作Q 是和动作G 相应的对变量W的read或write

如果A 先于B,那么P先于Q (这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同)

6.1 对于Long和double型变量的特殊规则

对于32位平台,64位的操作需要分两步来进行,与主存的同步。所以可能出现“半个变量”的状态。

在实际开发中,目前各种平台下的商用虚拟机几乎都选择把64位数据的读写操作作为原子操作来对待,因此我们在编码时一般不需要把用到的long和double变量专门声明为volatile。

6.2 内存屏障

6.2.1 分类

Load Barrier 读屏障

Store Barrier写屏障

6.2.2 有序性(Ordering)

JMM中程序的天然有序性可以总结为一句话:

如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。

前半句是指“线程内表现为串行语义”

后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象

Java提供了volatile和synchronized保证线程之间操作的有序性

volatile本身就包含了禁止指令重排序的语义

synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则来获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。

先行发生原则

如果JMM中所有的有序性都只靠volatile和synchronized,那么有一些操作将会变得很繁琐,但我们在编写Java并发代码时并没有感到这一点,这是因为Java语言中有一个先行发生(Happen-Before)原则。这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的主要依赖。

先行发生原则是指JMM中定义的两项操作之间的依序关系

如果说操作A先行发生于操作B,就是在说发生B前,A产生的影响能被B观察到,“影响”包含了修改内存中共享变量的值、发送了消息、调用了方法等。它意味着什么呢?如下例:

//线程A中执行

i = 1;

//线程B中执行

j = i;

//线程C中执行

i = 2;

下面是JMM下一些”天然的“先行发生关系,无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随意地重排序。

程序次序规则(Pragram Order Rule)

在一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环结构。

管程锁定规则(Monitor Lock Rule)

一个unlock先行发生于后面对同一个锁的lock。这里必须强调的是同一个锁,而”后面“是指时间上的先后。

volatile变量规则(Volatile Variable Rule)

对一个volatile变量的写操作先行发生于后面对这个变量的读取操作,这里的”后面“同样指时间上的先后顺序。

线程启动规则(Thread Start Rule)

Thread对象的start()方法先行发生于此线程的每一个动作。

线程终止规则(Thread Termination Rule)

线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值等作段检测到线程已经终止执行。

线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生。

对象终结规则(Finalizer Rule):一个对象初始化完成(构造方法执行完成)先行发生于它的finalize()方法的开始。

传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

一个操作”时间上的先发生“不代表这个操作会是”先行发生“,那

如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢?也是不成立的,一个典型的例子就是指令重排序。所以时间上的先后顺序与先生发生原则之间基本没有什么关系,所以衡量并发安全问题一切必须以先行发生原则为准。

3.2 作用

1.阻止屏障两侧的指令重排序

2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据

对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见

java的内存屏障实际上也是上述两种的组合,完成一系列的屏障和数据同步功能

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

volatile的内存屏障策略非常严格保守

在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障

在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障

由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性



推荐阅读
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 加密世界下一个主流叙事领域:L2、跨链桥、GameFi等
    本文介绍了加密世界下一个主流叙事的七个潜力领域,包括L2、跨链桥、GameFi等。L2作为以太坊的二层解决方案,在过去一年取得了巨大成功,跨链桥和互操作性是多链Web3中最重要的因素。去中心化的数据存储领域也具有巨大潜力,未来云存储市场有望达到1500亿美元。DAO和社交代币将成为购买和控制现实世界资产的重要方式,而GameFi作为数字资产在高收入游戏中的应用有望推动数字资产走向主流。衍生品市场也在不断发展壮大。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
author-avatar
12sadad
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有