目录
原子性
如何保证原子性
可见性
如何保证可见性
有序性
如何保证有序性
Java内存模型(JMM内存模型)
Java内存模型的一些关键概念:
主内存与工作内存交互协议
Java内存模型通过以下手段来确保多线程程序的正确性:
锁机制
volatile
volatile禁止指令重排序
Happens-Before
并发三大特性
原子性、可见性、有序性
原子性
原子性是指一个操作是不可中断的。一个原子操作是一个不可分割的整体,要么全部执行成功,要么全部不执行。在多线程环境下,当多个线程访问共享变量时,如果其中一个线程在执行某个操作,其他线程不能同时执行该操作。
public class AtomicTest {
private static volatile int counter = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
//synchronized (AtomicTest.class) {
counter++;
// }
}
});
thread.start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter);
}
}
正常执行后counter变量的值远远大于预期10000,加synchronized后counter变量为10000
如何保证原子性
1. 通过synchronized关键字保证原子性
2. 通过 Lock锁保证原子性
3. 通过 CAS保证原子性
可见性
可见性是指当一个线程修改了共享变量的值时,其他线程能够立即看到这个修改。在多线程环境下,由于线程之间的缓存机制,一个线程对共享变量的修改可能不会立即被其他线程看到。
public class VisibilityExample {
private static boolean stop = false;
public static void main(String[] args) {
// 线程1:修改共享变量
Thread thread1 = new Thread(() -> {
while (!stop) {
// do something
}
System.out.println("Thread 1 finished");
});
// 线程2:修改共享变量
Thread thread2 = new Thread(() -> {
stop = true;
System.out.println("Thread 2 set stop to true");
});
// 启动线程1
thread1.start();
// 稍等片刻
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动线程2
thread2.start();
}
}
在以上代码中,两个线程分别运行,并且共享一个stop变量。线程1在一个循环中检查stop变量是否为false,而线程2在启动后将stop变量设置为true。然而,由于没有同步机制,线程1可能不会立即看到线程2对stop变量的修改,导致线程1一直在循环中无法退出。
private static volatile boolean stop = false;
通过给stop变量增加volatile修饰,即可保证可见性。
如何保证可见性
通过volatile 关键字保证可见性
通过内存屏障保证可见性
通过synchronized 关键字保证可见性
通过Lock锁保证可见性
有序性
有序性是指程序执行的顺序按照代码的先后顺序执行。在多线程环境下,由于指令重排序等优化,有时候线程执行的顺序可能与代码的顺序不一致。
public class ReOrderTest {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i=0;
while (true) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//用于调整两个线程的执行顺序
shortWait(20000);
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x==0&&y==0){
break;
}
}
}
public static void shortWait(long interval){
long start = System.nanoTime();
long end;
do{
end = System.nanoTime();
}while(start + interval >= end);
}
}
执行结果:x,y出现了0,0的结果,程序终止。出现这种结果有可能是重排序导致的。
如何保证有序性
通过volatile 关键字保证有序性
通过内存屏障保证有序性
通过synchronized关键字保证有序性
通过Lock锁保证有序性
Java内存模型(JMM内存模型)
Java内存模型(Java Memory Model,JMM)是Java程序中多线程并发访问共享变量时,对内存操作行为的一种规范。它定义了在多线程环境中,线程如何与主内存和工作内存交互,以确保并发程序的正确性。
Java内存模型的一些关键概念:
1. 主内存(Main Memory): 主内存是所有线程共享的内存区域,包含所有的共享变量。所有的线程都可以访问主内存。
2. 工作内存(Working Memory): 每个线程有自己的工作内存,存储了该线程使用到的变量的副本。线程对变量的所有操作都在工作内存中进行,不直接读写主内存。
3. 共享变量: 多个线程之间可以共享的变量,例如类的静态变量或实例变量。
4. 原子性(Atomicity): 单个的读/写操作是原子的,即要么完成,要么不开始。
5. 可见性(Visibility): 当一个线程修改了共享变量的值,其他线程应该能够立即看到这个变化。
6. 有序性(Ordering): 程序执行的顺序与代码中的顺序一致。
主内存与工作内存交互协议
从工 作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种原子操作来完成:
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁 定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要 使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机 遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操 作。
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
Java内存模型通过以下手段来确保多线程程序的正确性:
1. 锁机制: 使用synchronized关键字或显式锁来保护共享数据,确保在同一时刻只有一个线程可以访问临界区。
2. volatile关键字: 用于修饰共享变量,保证可见性。对一个volatile变量的写操作将立即刷新到主内存,而读操作将从主内存中加载最新的值。
3. final关键字: 被final修饰的字段在构造函数结束之前就已经对其他线程可见。
4. Happens-Before关系: JMM定义了Happens-Before的规则,确保在某个操作之前的操作对其可见。
锁机制
1. 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。
2. 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
synchronized关键字的作用是确保多个线程访问共享资源时的互斥性和可见性。在获取锁之前,线程 会将共享变量的最新值从主内存中读取到线程本地的缓存中,释放锁时会将修改后的共享变量的值刷 新到主内存中,以保证可见性。
volatile
1. 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
2. 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。
volatile禁止指令重排序
Happens-Before
Happens-Before是Java内存模型(Java Memory Model,JMM)中一个重要的概念,用于规定多线程程序中操作之间的执行顺序和可见性关系。这个概念确保了在程序中对共享变量的操作是按照一定顺序执行的,以避免出现不确定的结果和数据竞争问题。
如果操作A在操作B之前发生在之前(happens-before),那么操作A对于操作B来说,必定是可见的,而且操作A的效果将被操作B看到。
Happens-Before部分关系规则:文章来源:https://www.toymoban.com/news/detail-805601.html
1. 程序顺序规则(Program Order Rule): 在一个线程中,操作按照程序代码的先后顺序执行。
2. 锁定规则(Lock Rule): 释放锁的操作Happens-Before于后续对同一锁的加锁操作。
3. volatile变量规则(Volatile Variable Rule): 对volatile变量的写操作Happens-Before于后续对这个变量的读操作。
4. 传递性(Transitivity): 如果操作A Happens-Before于操作B,且操作B Happens-Before于操作C,那么操作A Happens-Before于操作C。
5. 线程启动规则(Thread Start Rule): 线程的启动操作Happens-Before于该线程的任何操作。
6. 线程终止规则(Thread Termination Rule): 线程的所有操作Happens-Before于其他线程检测到该线程已经终止。文章来源地址https://www.toymoban.com/news/detail-805601.html
到了这里,关于并发编程之三大特性及JMM内存模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!