volatile是java虚拟机提供的轻量级的同步机制。
三大特性:保证可见性,不保证原子性,禁止指令重排。
JVM(java虚拟机)
JMM(java内存模型)
代码演示volatile的可见性
没有volatile的
package com.company;
public class Main {
public static void main(String[] args) {
// write your code here
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" LMXDSB"+ myData.number);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add();
System.out.println(Thread.currentThread().getName()+" LMXxSB"+ myData.number);
},"LMX").start();
//第二个线程就是main线程
while (myData.number == 0){
//main就一直在这里循环等待,直到number值不等于0
}
System.out.println(Thread.currentThread().getName()+myData.number);
}
}
class MyData{
int number = 0;
public void add(){
this.number = 60;
}
}
number值虽然已被修改,但是没有通知其它线程,main线程不知道number已被修改,所以循环体判断一直为真,一直循环
有volatile的
package com.company;
public class Main {
public static void main(String[] args) {
// write your code here
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" LMXDSB"+ myData.number);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add();
System.out.println(Thread.currentThread().getName()+" LMXxSB"+ myData.number);
},"LMX").start();
//第二个线程就是main线程
while (myData.number == 0){
//main就一直在这里循环等待,直到number值不等于0
}
System.out.println(Thread.currentThread().getName()+myData.number);
}
}
class MyData{
volatile int number = 0;
public void add(){
this.number = 60;
}
}
通知main线程,number值已改为60,可以结束循环
volatile不保证原子性代码演示
package com.company;
/**
* 1.验证volatile的可见性
* 1.1假如int number - 0,number前边没有volatile关键字修饰,没有可见性
* 1,2添加volatile,可以解决可见性问题
* 2.验证volatile不保证原子性
* 2.1原子性指的是什么意思?
* 不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或被分割。
* 需要整体完整。要么同时成功,要么同时失败
* 2.2案例演示
*/
public class Main {
public static void main(String[] args) {
// write your code here
MyData myData = new MyData();
for(int i = 1; i <=20; i++){
new Thread(()->{
for (int j = 0; j <= 1000; j++) {
myData.addPlus();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算后,再用main线程取得最终的结果值看是多少?
while (Thread.activeCount() > 2){
//这步是统计线程数,2是因为默认有两个线程:main线程和gc线程,>2说明上边20个线程没有全部计算完
Thread.yield();//线程让步
}
System.out.println(Thread.currentThread().getName() + myData.number);
}
//volatile可以保证可见性,及时通知其它线程,主物理内存的值已经被修改
private static void seeOKByVolatile() {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" LMXDSB"+ myData.number);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add();
System.out.println(Thread.currentThread().getName()+" LMXxSB"+ myData.number);
},"LMX").start();
//第二个线程就是main线程
while (myData.number == 0){
//main就一直在这里循环等待,直到number值不等于0
}
System.out.println(Thread.currentThread().getName()+myData.number);
}
}
class MyData{
volatile int number = 0;
public void add(){
this.number = 60;
}
public void addPlus(){
number++;
}
}
1.加synchronized(在addPlus方法前加)
2.使用juc下的AtomicInteger
package com.company;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 1.验证volatile的可见性
* 1.1假如int number - 0,number前边没有volatile关键字修饰,没有可见性
* 1,2添加volatile,可以解决可见性问题
* 2.验证volatile不保证原子性
* 2.1原子性指的是什么意思?
* 不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或被分割。
* 需要整体完整。要么同时成功,要么同时失败
* 2.2案例演示
* 2.3 why
* 多个线程不同步,会产生覆盖,丢值
* 2.4如何解决原子性问题
* 1.加synchronized(在addPlus方法前加)
* 2.使用juc下的AtomicInteger
*/
public class Main {
public static void main(String[] args) {
// write your code here
MyData myData = new MyData();
for(int i = 1; i <=20; i++){
new Thread(()->{
for (int j = 0; j < 1000; j++) {
myData.addPlus();
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算后,再用main线程取得最终的结果值看是多少?
while (Thread.activeCount() > 2){
//这步是统计线程数,2是因为默认有两个线程:main线程和gc线程,>2说明上边20个线程没有全部计算完
Thread.yield();//线程让步
}
System.out.println(Thread.currentThread().getName() + "xxx" + myData.number);
System.out.println(Thread.currentThread().getName() + "ccc" + myData.atomicInteger);
}
//volatile可以保证可见性,及时通知其它线程,主物理内存的值已经被修改
private static void seeOKByVolatile() {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" LMXDSB"+ myData.number);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add();
System.out.println(Thread.currentThread().getName()+" LMXxSB"+ myData.number);
},"LMX").start();
//第二个线程就是main线程
while (myData.number == 0){
//main就一直在这里循环等待,直到number值不等于0
}
System.out.println(Thread.currentThread().getName()+myData.number);
}
}
class MyData{
volatile int number = 0;
public void add(){
this.number = 60;
}
public void addPlus(){
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic(){
atomicInteger.getAndDecrement();
}
}
禁止指令重排
指令重排:
在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息
重排1:
重排2:
线程执行一半被挂起(b=1,a=2,x=a,y=b)
重排3:
正常执行后,a=6.
指令重排后,语句2在语句1前边,语句2先执行, 然后被挂起,语句3执行,此时,a=5.
禁止指令重排小结:
package com.company;
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName() + "\t我是构造方法");
}
//DCL(double check lock双端检锁机制)
private static SingletonDemo getInstance(){
if (instance==null){
synchronized (SingletonDemo.class){
if (instance == null){
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
// System.out.println(SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance());
// 并发多线程后,情况发生了很大的变化
for(int i = 1; i <=10; i++){
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}