1 可见性:一个线程对共享变量值的修改,能够及时的被其他线程看到。
2 共享变量:如果一个变量在多个线程的工作内存中都能存在副本,那么这个变量就是这几个线程的共享变量。
所有的变量都存在主内存中,
每个变量都有自己独立的工作内存,里面保存该线程使用到的变量的副本。
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读取。
不同线程之间不能互相访问变量,只能通过主内存来访问。
共享变量的可见性原理,
线程1对共享变量的修改要想被线程2及时的看到,由线程1将共享变量刷新到主内存中,然后主内存将最新的共享变量的值更新到工作内存2中。
Synchronized能够实现:
原子性,
线程解锁时,必须把共享变量的值刷新到主内存中。
线程解锁时,将清空工作内存中共享变量的值,从而使用共享变量时从主内存中获取最新的值。
线程执行互斥代码的过程:
1、获得互斥锁,
2、清空工作内存,
3、从主内存中拷贝变量到工作内存中。
4、执行代码。
5 、将更改后的共享变量的值刷新到主内存中。
6、释放互斥锁。
可见性
volatile关键字:
能够保证volatile变量的可行性
不能保证volatile变量复合操作的原子性
通过加入内存屏障和禁止重排序优化来实现的。
对volatile变量执行写操作时,会在写操作后加入一条store屏障指令。
对volatile变量执行读操作时,会在读操作前加入一条load屏障指令。
通俗的讲,volatile变量在每次被线程访问时,都强迫从主内存中重新读取该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存中,这样任何时刻,都能保证不同的线程看到的都是最新的值。
线程写volatile变量的过程:
1 改变线程工作内存中volatile变量的值,
2 将改变后的副本的值从工作内存刷新到主内存。
线程读volatile变量的过程:
1 从主内存中读取volatile变量的最新值到线程的工作线程中。
2 从工作内存中读取volatile变量的副本。
public class VolatileDemo {
private volatile Integer number = 0;
public Integer getNumber() {
return this.number;
}
public void increase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.number++;
//上面操作应该分三部分
//读取number的值,然后加1,然后写入到主内存中
//
}
public static void main(String[] args) {
final VolatileDemo volatileDemo = new VolatileDemo();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileDemo.increase();
}
}).start();
}
// 如果还有子线程在运行,主线程就会让出CPU资源,
// 直到所有的子线程都运行完,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("number:" + volatileDemo.getNumber());
}
}
可能出现小于100的情况
1 线程A读取number的值
2 线程B读取number的值,
3 线程B执行加1的操作。
4 线程B写入最新的number的值。
主内存number-6,
线程B工作内存:number=6
线程B工作内存:number=5
原因:不是原子操作
解决方案:
1 加同步关键字:
public class VolatileDemo {
private Integer number = 0;
public Integer getNumber() {
return this.number;
}
public void increase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
**//同步代码块
synchronized (this) {
this.number++;
}**
}
public static void main(String[] args) {
final VolatileDemo volatileDemo = new VolatileDemo();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileDemo.increase();
}
}).start();
}
// 如果还有子线程在运行,主线程就会让出CPU资源,
// 直到所有的子线程都运行完,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("number:" + volatileDemo.getNumber());
}
}
2加锁
public class VolatileDemo {
private Lock lock=new ReentrantLock();
private Integer number = 0;
public Integer getNumber() {
return this.number;
}
public void increase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加锁
lock.lock();
try{
this.number++;
}finally{
//释放锁 在finally中防止出现异常。
lock.unlock();
}
}
public static void main(String[] args) {
final VolatileDemo volatileDemo = new VolatileDemo();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileDemo.increase();
}
}).start();
}
// 如果还有子线程在运行,主线程就会让出CPU资源,
// 直到所有的子线程都运行完,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("number:" + volatileDemo.getNumber());
}
}
对volatile变量的安全操作,必须满足:
1 对变量的写入操作不能依赖当前值
不满足:number++、count=count*5等
满足:boolean变量,记录温度变化的变量
2 该变量没有包含在具有其他变量的不变式中,
不满足:不变是low
public class TestMain {
private static boolean ready;
private static Integer number;
private static class ReadThread extends Thread {
@Override
public void run() {
while (!ready) {
Thread.yield();
}
System.out.println(number);
}
}
public static void main(String[] args) {
new ReadThread().start();
number = 42;
ready = true;
}
}