- 为什么非volatile变量也有线程可见性?
- 不加volatile也可以看到变量变化是为什么?
- Thread.sleep() 和 System.out.println() 与内存可见性的影响
- Thread.sleep() 对线程可见性的影响?
- 为什么volatile保证不了线程安全
- Java中的Monitor监视器是什么?
1、测试Volatile是否能保证线程可见性
@Slf4j
public class Volatile {
// ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
// 创建任务线程
Task task = new Task();
// 启动子线程
new Thread(task, "子线程1")
.start();
try {
// 主线程修改共享变量, 查看是否能停止子线程
Thread.sleep(3000);
task.buffer = false;
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程: " + Thread.currentThread().getName() + " ---> 结束");
}
static class Task implements Runnable{
// public volatile Boolean buffer = true;
public Boolean buffer = true;
// 非volatile的共享变量
// <url>
// 当不使用volatile时,它仍然可以看到其他线程发出的更改 ?
// 原因1: System.out.println()会执行一段synchronized同步代码,而synchronized会让当前线程读取到最新的高速缓存值
// 原因2: Thread.sleep结束后, 会重新从主内存获取最新值load到线程工作内存
// https://www.jianshu.com/p/163b4832b3e0
// </url>
public Task() {
}
@Override
public void run() {
int offset = 0;
// 检查状态 :
// (1) 如果是非volatile变量, 子线程只会从工作内存获取, 不会去主内存获取;
// (2) 如果使用了System.out.println, 由于println的实现类PrintStream中使用了synchronized, 则会从主存刷新数据到工作内存, 保证线程可见性
// (3) 如果使用了Thread.sleep, 当子线程休眠结束, 会重新从主内存获取数据到工作内存,从而达到刷新左右,保证线程可见性
while (buffer){
// try {
// Thread.sleep(200);
// // System.out.println("线程: " + Thread.currentThread().getName() + "======> " + (++offset));
// //log.info("线程: " + Thread.currentThread().getName() + "======> " + (++offset));
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
// System.out.println("线程: " + Thread.currentThread().getName() + " ---> 结束");
log.info("线程: " + Thread.currentThread().getName() + " ---> 结束");
}
}
}
2、Volatile + CAS, 在不加锁的情况下, 实现线程安全
/**
* Volatile + CAS, 在不加锁的情况下, 实现线程安全
*/
static class Volatile_AND_CAS {
/*
* 1、volatile只能保证线程可见性, 不能保证线程安全性;
* 原因是i++不是原子的, 存在多个步骤:
* 1) 从主内存load最新的i值0 , 加载到线程A的工作内存
* 2) 读取线程A工作内存, i=0, 然后放到操作数栈进行 temp = 0 + 1;
* 3) 此时线程A失去cpu时间片, 同时线程B执行了i++, 将主内存的i值变为1;
* 4) 此时线程A获取cpu时间片, 继续执行, 由于i是volatile标识的变量, 会从主内存获取最新值 i=1; temp = 1;
* 5) 由于已经执行过了tem = 0 + 1, 则最后线程A将temp = 1 save到主内存中, 出现了两次执行, i却不等于2的情况;
*/
// private static volatile int i = 0;
/*
* 2、无法保证线程可见性、无法保证线程安全(操作原子性)
*/
// private static int i = 0;
/**
* 3、java.util.concurrent.atomic.AtomicInteger
* 1) 它的内部的成员变量value是由volatile修饰, 可以获取到最新值;
* 2) do - while 死循环, 进行CAS操作(compare and set);
* 3) do里面获取的最新值的version, 然后while里面执行操作compareAndSwapInt = (i+1, 且version = 刚才获取的值的version),
* 4) 如果线程A执行compareAndSwapInt操作之前, 线程B将 int volatile value修改了, version值则加1, 之后线程A执行compareAndSwapInt会失败, 因为version错误, 而会继续死循环获取最新值;
*/
private static AtomicInteger i = new AtomicInteger(0);
/**
* 测试
*/
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(2);
// 启动线程1
new Thread(() -> {
for (int j = 0; j < 100; j++) {
// i++;
i.getAndIncrement();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
countDownLatch.countDown();
}).start();
// 启动线程2
new Thread(() -> {
for (int j = 0; j < 100; j++) {
// i++;
i.getAndIncrement();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
countDownLatch.countDown();
}).start();
try {
// 主线程等待2个线程执行完毕
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果: " + i);
}
}