在多线程使用volatile关键字

本文探讨了Java中Volatile关键字的作用与限制,通过实验演示了Volatile虽然能确保可见性,但并不能保证原子性,特别是在自增操作时。文章还提供了一种使用synchronized方法替代Volatile解决线程安全问题的方法。
构建了100个线程, Volatile变量具有 synchronized 的可见性特性,但是不具备原子特性。
同时访问操作i,如果i在使用i++时候是对自身变量的操作,实际它这样的操作不是原子操作。下边我们用一段代码试试。
/**
* Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。 其中 Volatile
* 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。
*
* @author Janle
*
*/
public class volatile关键字 {

public static void main(String[] args) {
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
// 建立100个线程
threads[i] = new ThreadVolatile();

for (int i = 0; i < threads.length; i++)
// 运行刚才建立的100个线程
threads[i].start();
for (int i = 0; i < threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 如果对n的操作是原子级别的,最后输出的结果应该为n=1000,很多时侯输出的n都小于1000.这说明n=n+1不是原子级别的操作。
* 原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,
* 也就是说如下的表达式都不是原子操作:
* n = n + 1;
* n++;
*/
System.out.println("n=" + ThreadVolatile.n);
}
}

class ThreadVolatile extends Thread {
public static volatile int n = 0;
public static synchronized void inc() {
n++;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
/**
* 这里注意:
* 当变量的值由自身的上一个决定时,如n=n+1、n++等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,
* 如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
*/
//n = n + 1;
inc();//// n = n + 1 改成了 inc();使用这个才能保证是原来的操作,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作
sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}

总结:
用写操作时候不能依赖于当前值【当前获得的值不能被修改】。
该变量没有包含在具有其他变量的计算公式中。

volatile修饰设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
### Java `volatile` 关键字多线程编程中的作用 #### 可见性 当多个线程访问共享变量时,为了提高性能,每个线程可能会把该变量缓存在本地内存中。这可能导致某些线程看到的是过期的数据副本而不是最新的数据。使用 `volatile` 修饰符可以确保任何对该字段的更新都会立即写回到主存,并且每次读取都从主存获取最新值,从而解决了不同线程间的工作内存不一致的问题[^2]。 ```java public class VolatileExample { private volatile boolean isRunning = true; public void stop() { this.isRunning = false; } public void doTask() { while (isRunning) { // 执行任务... } } } ``` 上述例子展示了如何利用 `volatile` 来控制循环条件的安全退出,防止由于工作内存与主存之间的差异而导致的任务无法停止的情况发生。 #### 原子性 需要注意的是,尽管 `volatile` 提供了可见性的保障,但它并不提供操作上的原子性保护。这意味着像递增 (`i++`) 或者复合赋值语句这样的动作并不是原子化的,仍然可能存在竞态条件的风险。因此,在涉及复杂状态变化的地方应考虑采用更严格的同步措施,比如 `synchronized` 方法/块或是借助并发包下的工具类[^4]。 ```java // 错误示范:非原子操作即使加上 volatile 也无法保证正确性 private volatile int counter = 0; void incrementNonAtomic() { counter++; // 非原子操作 } // 正确做法之一:使用 synchronized 实现原子化 private final Object lock = new Object(); void incrementSafelyWithSync() { synchronized(lock){ ++counter; } } ``` #### 使用场景 - **标志位**:用于表示某种状态的变化,如上面提到的例子那样作为终止信号。 - **双重检查锁定模式(DCL)** 中的部分实现细节也需要依赖于 `volatile` 的特性来避免指令重排序带来的问题。 - 当只需要简单的布尔型或其他基本类型的即时通知而不需要复杂的逻辑判断时适用。 #### 最佳实践 1. 尽量只对那些确实需要跨线程通信的状态标记应用 `volatile` 属性; 2. 对于复合的操作(例如先读再写的组合),应该优先选用更高层次的同步手段而非单纯依靠 `volatile`; 3. 如果业务需求涉及到大量频繁的竞争资源,则建议评估是否有必要引入更加高效专业的并发结构或算法替代传统锁机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值