Java内存模型

简单理解就是变量存储在主内存中,当开启个新线程是会有一个工作内存,线程使用的共享内存是从主内存copy过来的,如果线程A对共享变量进行修改之后,在某个时间会将工作内存中的共享变量新值写入主内存,不存在及时写入主内存,这样在开启多个线程并访问相同变量情况下,会出现数据不正确的问题,即A线程已经改变了值,但是B线程中的工作内存里面的共享变量还是旧值。
原子性
一个操作或者多个操作,要么全部执行,要么都不执行,在Java中基本数据类型的变量的读取和赋值是原子性的,例如: x = 10;
可见性
在多线程的条件下,一个线程修改了共享变量的值,其他线程能指导能看见共享内存已经改变
有序性:
即程序执行顺序按照代码的先后顺序执行,这里为什么要说有序呢,因为CPU为了提高程序的运行效率,会对代码进行优化,它不保证执行顺序和代码的顺序一致,虽然处理器会对指令进行重排序,但是他会保证最终执行结果和顺序执行代码结果一致,
volatile关键字
1.保证可见性:
一旦一个共享变量被volatile修饰之后,那么他就具有两层含义
a.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
b.禁止进行指令重排序
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
这里可能会发生线程2执行完stop = true后转去做其他事了,没有将stop写入主内存,那么线程1就会一直循环
使用volatile关键字修饰后线程2修改了值后会立即写入主线程,导致线程1的缓存行无效,那么线程1就会去重新从主内存中取值,那么线程1取到的就是新的值了
2.volatile不能确保原子性
public class Nothing {
private volatile int inc = 0;
private volatile static int count = 10;
private void increase() {
++inc;
}
public static void main(String[] args) {
int loop = 10;
Nothing nothing = new Nothing(); while (loop-- > 0) {
nothing.operation();
}
}
private void operation() {
final Nothing test = new Nothing();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000000; j++) {
test.increase();
}
--count;
}).start();
}
// 保证前面的线程都执行完
while (count > 0) {
}
System.out.println("最后的数据为:" + test.inc); }
}
这里的结果部位100000,因为会发生这样的情况,就是线程1读取了inc的值为10,之后线程1被阻塞,线程2去读取inc的值为10,线程2进行+1 = 11,把11写入主内存,线程1切换回来之后,把11写入线程,这样两次循环就只做了一次加1的操作
解决方法:通过sychronized或lock进行加锁
3.volatile保证了有序性
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定 全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量的读操作或者写操作的语句放在其 后面执行,也不能把volatile变量后面的语句放到其前面执行。
乐观锁和悲观锁
悲观锁:
当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过 notify(),notifyAll()唤醒回来。 在某个资源不可用的时候,就将cpu让出,把当前等 待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤 醒,让他进入runnable状态等待cpu调度,典型的就是synchronized,他修饰的数据同一时刻只能被一个线程访问,其他访问的线程进入到挂起状态
乐观锁:
每次不加锁而是假设修改数据之前 其他线程一定不会修改,如果因为修改过产生冲突就失败就重试,直到成功为止。 在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重 试,直到成功为止,典型技术CAS,当同步冲突很少的时候CAS的效率很高,但是他存在三个问题:(1.ABA问题,就是当变量从A-B-A 时,会误认为没有改变值,其实他已经改变了,解决这个问题我们加入了版本号,2.循环时间开销大问题,3.只能保证一个共享变量的原子操作问题)
网友评论