-
2021-11-06 11:21:11
static关键字主要有两种作用:
第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。
第二,实现某个方法或属性与类而不是对象关联在一起
具体而言,在Java语言中,static主要有4中使用情况:成员变量、成员方法、代码块和内部类
(1)static成员变量:
Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量。静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使用了。对静态变量的引用有两种方式,分别是“类.静态变量"和”对象.静态变量"
实例变量属于对象,只有对象被创建后,实例变量才会被分配内存空间,才能被使用,它在内存中存在多个复制,只有用“对象.实例变量”的方式来引用。
(2)static成员方法:Java中提供了static方法和非static方法。static方法是类的方法,不需要创建对象就可以被调用,而非static方法是对象的方法,只有对象被创建出来后才可以被使用
static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和成员方法,因为当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。同理,static方法也不能访问非static类型的变量。
1. 全局静态变量
定义:在全局变量前加上关键字static;
内存中的位置:静态存储区;
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。2. 局部静态变量
定义:在局部变量前加上关键字static;
内存中的位置:静态存储区;
初始化:未经初始化的局部静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。这种变量在程序执行之前就创建,在程序执行的整个周期都存在。3. 静态函数
定义:在函数返回类型前加static;
作用域:函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;
注意:不要在头文件中声明static的全局函数(变量),原因是:包含了该头文件的所有源文件中都定义了这些函数(变量),即该头文件被包含了多少次,这些函数(变量)就定义了多少次。
不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰。
4. 类的静态成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用,包括派生类的对象。
静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。
静态成员变量可以作为成员函数的参数,而普通成员变量不可以。
更多相关内容 -
volatile关键字主要作用与使用
2019-06-29 10:49:57volatile关键字主要作用与使用1.作用1未使用volatile关键字执行结果修改变量flag的关键字volatile执行结果2分析java内存模型 1.作用 实现线程本地内存与主内存之间的数据更新,实现各线程本地内存的数据操作,可以向...volatile关键字主要作用与使用
1.作用
实现线程本地内存与主内存之间的数据更新,实现各线程本地内存的数据操作,可以向主内存中数据一样可以共享。
1未使用volatile关键字
package ThreadVolatile; import com.sun.jna.platform.win32.FlagEnum; import com.zx.threadlock.threadDemo; public class VolatileDemo { public static void main(String[] args) throws InterruptedException { Thread1 t1 = new Thread1(); t1.start(); Thread.sleep(300); t1.isRun(false); } } class Thread1 extends Thread{ public boolean flag = true; @Override public void run() { System.out.println("子线程开始执行"); while(flag) { } System.out.println("子线程结束执行"); } public void isRun(boolean flag) { this.flag = flag; } }
执行结果
线程持续执行,未能结束。isRun方法未能将flag设置为的false的状态共享,主线程未能获得变化,所以一直以true执行,出现线程安全问题。修改变量flag的关键字volatile
class Thread1 extends Thread{ public volatile boolean flag = true; @Override public void run() { System.out.println("子线程开始执行"); while(flag) { } System.out.println("子线程结束执行"); } public void isRun(boolean flag) { this.flag = flag; } }
执行结果
子线程成功结束2分析
java内存模型
因为线程之间开始默认为不可见的,所以在线程t1调用isRun方法,将flag设置为false时,主线程没有获取到更新,从而使得主线程一直以flag为true执行,使得子线程无法关闭,在使用volatile关键字后,flag为可见,主内存可以获得子线程中本地内存对flag数据的操作结果。volatile非原子性
线程三大大特性之一的是原子性,为保证数据的一致性来确保线程安全。volatile主要作用为更新本地内存中的数据,并不会保证操作的原子性。volatile非原子性
-
Java中volatile关键字的作用与用法详解
2020-09-01 12:50:46volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。这篇文章主要介绍了Java中volatile关键字的作用与用法详解的相关资料,需要的朋友可以参考下 -
详解c++中的 static 关键字及作用
2020-08-25 01:13:24主要介绍了c++中的 static 关键字,在我们日常使用过程中,static通常有两个作用,具体内容在文中给大家详细介绍,需要的朋友可以参考下 -
JAVA关键字及作用详解
2020-08-31 06:29:14本文主要介绍了Java关键字及作用,具有很好的参考价值,下面跟着小编一起来看下吧 -
Volatile关键字的作用
2022-04-24 11:42:23Volatile关键字的作用主要有如下两个: 1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。 2. 顺序一致性:禁止指令重排序。 一、线程可见性 我们先通过一个例子来看看线程的可见性...Volatile关键字的作用主要有如下两个:
1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
2. 顺序一致性:禁止指令重排序。一、线程可见性
我们先通过一个例子来看看线程的可见性:
public class VolatileTest { boolean flag = true; public void updateFlag() { this.flag = false; System.out.println("修改flag值为:" + this.flag); } public static void main(String[] args) { VolatileTest test = new VolatileTest(); new Thread(() -> { while (test.flag) { } System.out.println(Thread.currentThread().getName() + "结束"); }, "Thread1").start(); new Thread(() -> { try { Thread.sleep(2000); test.updateFlag(); } catch (InterruptedException e) { } }, "Thread2").start(); } }
打印结果如下,我们可以看到虽然线程Thread2已经把flag 修改为false了,但是线程Thread1没有读取到flag修改后的值,线程一直在运行
修改flag值为:false
我们把flag 变量加上volatile:
volatile boolean flag = true;
重新运行程序,打印结果如下。Thread1结束,说明Thread1读取到了flage修改后的值
修改flag值为:false Thread1结束
说到可见性,我们需要先了解一下Java内存模型,Java内存模型如下所示:
线程之间的共享变量存储在主内存中(Main Memory)中,每个线程都一个都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。所以当一个线程把主内存中的共享变量读取到自己的本地内存中,然后做了更新。在还没有把共享变量刷新的主内存的时候,另外一个线程是看不到的。
如何把修改后的值刷新到主内存中的?
现代的处理器使用写缓冲区临时保存向内存写入的数据。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等向内存写入数据而产生的延迟。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,较少对内存总线的占用。但是什么时候写入到内存是不知道的。
所以就引入了volatile,volatile是如何保证可见性的呢?
在X86处理器下通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时,会多出lock addl。Lock前缀的指令在多核处理器下会引发两件事情:- 将当前处理器缓存行的数据写回到系统内存。
- 这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
如果声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的还是旧的,在执行操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
二、顺序一致性
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分为如下三种:
1属于编译器重排序,2和3属于处理器重排序。这些重排序可能会导致多线程程序出现内存可见性问题。
当变量声明为volatile时,Java编译器在生成指令序列时,会插入内存屏障指令。通过内存屏障指令来禁止重排序。
JMM内存屏障插入策略如下:
在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个StoreLoad屏障。
在每个volatile读操作后面插入一个LoadLoad,LoadStore屏障。Volatile写插入内存屏障后生成指令序列示意图:
Volatile读插入内存屏障后生成指令序列示意图:
通过上面这些我们可以得出如下结论:编译器不会对volatile读与volatile读后面的任意内存操作重排序;编译器不会对volatile写与volatile写前面的任意内存操作重排序。防止重排序使用案例:
public class SafeDoubleCheckedLocking { private volatile static Instance instane; public static Instance getInstane(){ if(instane==null){ synchronized (SafeDoubleCheckedLocking.class){ if(instane==null){ instane=new Instance(); } } } return instane; } }
创建一个对象主要分为如下三步:
- 分配对象的内存空间。
- 初始化对象。
- 设置instance指向内存空间。
如果instane 不加volatile,上面的2,3可能会发生重排序。假设A,B两个线程同时获取,A线程获取到了锁,发生了指令重排序,先设置了instance指向内存空间。这个时候B线程也来获取,instance不为空,这样B拿到了没有初始化完成的单例对象(如下图)
二、Volatile与Synchronized比较
- Volatile是轻量级的synchronized,因为它不会引起上下文的切换和调度,所以Volatile性能更好。
- Volatile只能修饰变量,synchronized可以修饰方法,静态方法,代码块。
- Volatile对任意单个变量的读/写具有原子性,但是类似于i++这种复合操作不具有原子性。而锁的互斥执行的特性可以确保对整个临界区代码执行具有原子性。
- 多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
- volatile是变量在多线程之间的可见性,synchronize是多线程之间访问资源的同步性。
参考:《Java并发编程的艺术》
-
volatile 关键字的作用
2022-05-08 01:16:47作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)volatile 是 Java 并发编程的重要组成部分,也是常见的面试题之一,它的主要作用有两个:保证内存的可见性和禁止指令重...作者 | 磊哥
来源 | Java面试真题解析(ID:aimianshi666)
转载请联系授权(微信ID:GG_Stone)
volatile 是 Java 并发编程的重要组成部分,也是常见的面试题之一,它的主要作用有两个:保证内存的可见性和禁止指令重排序。下面我们具体来看这两个功能。
内存可见性
说到内存可见性问题就不得不提 Java 内存模型,Java 内存模型(Java Memory Model)简称为 JMM,主要是用来屏蔽不同硬件和操作系统的内存访问差异的,因为在不同的硬件和不同的操作系统下,内存的访问是有一定的差异得,这种差异会导致相同的代码在不同的硬件和不同的操作系统下有着不一样的行为,而 Java 内存模型就是解决这个差异,统一相同代码在不同硬件和不同操作系统下的差异的。
Java 内存模型规定:所有的变量(实例变量和静态变量)都必须存储在主内存中,每个线程也会有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量,如下图所示:
然而,Java 内存模型会带来一个新的问题,那就是内存可见性问题,也就是当某个线程修改了主内存中共享变量的值之后,其他线程不能感知到此值被修改了,它会一直使用自己工作内存中的“旧值”,这样程序的执行结果就不符合我们的预期了,这就是内存可见性问题,我们用以下代码来演示一下这个问题:
private static boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!flag) { } System.out.println("终止执行"); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("设置 flag=true"); flag = true; } }); t2.start(); }
以上代码我们预期的结果是,在线程 1 执行了 1s 之后,线程 2 将 flag 变量修改为 true,之后线程 1 终止执行,然而,因为线程 1 感知不到 flag 变量发生了修改,也就是内存可见性问题,所以会导致线程 1 会永远的执行下去,最终我们看到的结果是这样的:
如何解决以上问题呢?只需要给变量 flag 加上 volatile 修饰即可,具体的实现代码如下:
private volatile static boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!flag) { } System.out.println("终止执行"); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("设置 flag=true"); flag = true; } }); t2.start(); }
以上程序的执行结果如下图所示:
禁止指令重排序
指令重排序是指编译器或 CPU 为了优化程序的执行性能,而对指令进行重新排序的一种手段。
指令重排序的实现初衷是好的,但是在多线程执行中,如果执行了指令重排序可能会导致程序执行出错。指令重排序最典型的一个问题就发生在单例模式中,比如以下问题代码:
public class Singleton { private Singleton() {} private static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { // ① synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // ② } } } return instance; } }
以上问题发生在代码 ② 这一行“instance = new Singleton();”,这行代码看似只是一个创建对象的过程,然而它的实际执行却分为以下 3 步:
创建内存空间。
在内存空间中初始化对象 Singleton。
将内存地址赋值给 instance 对象(执行了此步骤,instance 就不等于 null 了)。
如果此变量不加 volatile,那么线程 1 在执行到上述代码的第 ② 处时就可能会执行指令重排序,将原本是 1、2、3 的执行顺序,重排为 1、3、2。但是特殊情况下,线程 1 在执行完第 3 步之后,如果来了线程 2 执行到上述代码的第 ① 处,判断 instance 对象已经不为 null,但此时线程 1 还未将对象实例化完,那么线程 2 将会得到一个被实例化“一半”的对象,从而导致程序执行出错,这就是为什么要给私有变量添加 volatile 的原因了。
要使以上单例模式变为线程安全的程序,需要给 instance 变量添加 volatile 修饰,它的最终实现代码如下:
public class Singleton { private Singleton() {} // 使用 volatile 禁止指令重排序 private static volatile Singleton instance = null; // 【主要是此行代码发生了变化】 public static Singleton getInstance() { if (instance == null) { // ① synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // ② } } } return instance; } }
总结
volatile 是 Java 并发编程的重要组成部分,它的主要作用有两个:保证内存的可见性和禁止指令重排序。volatile 常使用在一写多读的场景中,比如 CopyOnWriteArrayList 集合,它在操作的时候会把全部数据复制出来对写操作加锁,修改完之后再使用 setArray 方法把此数组赋值为更新后的值,使用 volatile 可以使读线程很快的告知到数组被修改,不会进行指令重排,操作完成后就可以对其他线程可见了。
是非审之于己,毁誉听之于人,得失安之于数。
公众号:Java面试真题解析
面试合集:https://gitee.com/mydb/interview
往期推荐
面试突击43:lock、tryLock、lockInterruptibly有什么区别?
面试突击42:synchronized和ReentrantLock有什么区别?
-
C#中partial关键字的作用
2020-08-31 05:32:50主要介绍了C#中partial关键字的作用详解,包括局部类型概念和注意点介绍,非常不错,具有参考借鉴价值,需要的的朋友参考下 -
this关键字的作用
2019-04-30 18:30:03作用: 1、调用本类中的方法 2、表示类中的属性 3、可以使用this调用本类的构造方法 4、this表示当前对象 1、调用本类中的方法 public class ThisDemo { public static void main(String[] args) { DemoThis d=... -
浅谈Java中static关键字的作用
2020-08-26 04:43:50主要介绍了Java中static关键字的作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
static关键字有什么作用
2021-03-09 00:29:24今天主要学习下Java语言中的static关键字。static关键字的含义及使用场景static是Java50个关键字之一。static关键字可以用来修饰代码块表示静态代码块,修饰成员变量表示全局静态成员变量,修饰方法表示静态方法。... -
Java 常用关键字的作用
2021-03-15 01:38:13主篇主要是对 static、final、super、this、native、transient这6个关键字进行简单讲解。一、staticstatic关键字可以修饰变量、方法、代码块及内部类。/*** static 关键字学习* @author kobe**/public class ... -
static关键字的作用是什么
2020-08-30 20:35:17static关键字的作用: 1、修饰函数的局部变量 有默认值0,只du执行一次,运行一开始就开zhi辟了内存,内存放在全局 2、修饰全局函数和全局变量 只能在本源文件使用 3、修饰类里面的成员变量 和1差不多,定义多个... -
试析SQL中EXISTS关键字的主要作用.pdf
2021-09-19 10:46:21试析SQL中EXISTS关键字的主要作用.pdf -
c#中sealed关键字的作用.doc
2020-09-12 11:15:19C#中sealed关键字的作用 sealed?的中文意思是密封顾名思义就是由它修饰的类或方法将不能被继承或是重写 在此类声明中使用sealed?可防止其它类继承此类在方法声明中使用sealed?修饰符可防止扩充类重写此方法文档来自... -
C语言const关键字的作用
2021-07-11 13:02:10所以const修饰也没有任何作用。 -> const 修饰函数的参数: void StringCopy(char *strDestination, const char *strSource); 给strSource 加上const修饰后,如果函数体内的语句试图改动strSource 的内容,编译器将... -
extern 关键字的作用
2019-08-08 12:33:44主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参... -
Java中static关键字的作用
2021-08-16 14:37:24static关键字主要有两种作用: 第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。 第二,实现某个方法或属性与类而不是对象关联在一起 具体而言,在Java语言中,static主要有4中使用情况:... -
C++中static关键字的作用
2020-01-22 11:41:37C++中的static关键字的作用总结 C++中的static的两种用法:一种是面向过程程序设计,第二种是面向对象程序设计。前者应用于普通变量和函数,不涉及类;后者涉及static在类中的作用。 1.面向过程设计中的static ... -
this 和 super 关键字的作用
2020-11-01 19:47:37this 和 super 关键字的作用 this 是对象内部指代自身的引用,同时也是解决成员变量和局部变量同 名问题;this 可以调用成员变量,不能调用局部变量;this 也可以调用成员 方法,但是在普通方法中可以省略 this,在... -
Java并发编程volatile关键字的作用
2020-08-18 21:25:25主要介绍了Java并发编程volatile关键字的作用,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下 -
volatile关键字作用
2020-02-21 20:24:51一、作用简述 内存可见性:保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制... -
详解js中call与apply关键字的作用
2020-10-21 02:04:11本文主要介绍js中call与apply关键字的作用以及它们的用法,具体实例如下,希望对大家有所帮助 -
面试|static 关键字有什么作用
2021-02-28 18:03:02今天主要学习下Java语言中的static关键字。static关键字的含义及使用场景static是Java50个关键字之一。static关键字可以用来修饰代码块表示静态代码块,修饰成员变量表示全局静态成员变量,修饰方法表示静态方法。... -
static关键字的作用详解
2020-11-25 14:42:33static关键字的作用(修饰类、方法、变量、静态块) 1、修饰函数的局部变量: 特点:有默认值0,只执行一次,运行一开始就开辟了内存,内存放在全局 2、修饰全局函数和全局变量: 特点:只能在本源文件使用 3、... -
C/C++中static关键字的作用
2020-07-25 10:41:51(一)在C语言中static的作用如下 (1)static修饰变量时,修饰的静态局部变量只执行一次,延长局部变量的生命周期,在程序运行结束后才会释放。 (2)static修饰全局变量时,全局变量只能在本文件中访问,不能在...