(1)代码理解
变量使用了volatile关键字
package automitic;
public class Mydata {
volatile int num=0;
public void addto60(){
this.num=60;
}
public void addplusplus(){
num++;
}
}
开启20个前程执行1000次num++
package automitic;
//验证volatile不保证原子性
public class VolatileDemo {
public static void main(String[] args) {
Mydata mydata=new Mydata(); //资源类
//创建20个线程,每个线程操作num++ 1000次
for(int i=0; i<20;i++){
new Thread(()->{
for(int j=0;j<1000;j++)
mydata.addplusplus();
},String.valueOf(i)).start();
}
//等待20个线程执行结束后,用main线程取值,如果volatile保证原子性,那应该会得到值20000
while (Thread.activeCount()>2){
//这里为什么用2,因为默认后台就会有两个线程,main线程和GC线程
Thread.yield();
}
//控制多线程结束后再..的最好方法
System.out.println(Thread.currentThread().getName()+"\t任务完成,num值现在为:"+mydata.num);
}
}
执行结果
发现执行多次的值都不一样,也都不是想当然的20000,当然也有是20000的可能,偶然而已。
所以证明volatile不保证原子性。
(2)原理
volatile不保证原子性,也再次体现了它是一个轻量级,乞丐版
-
为什么num++的值小于20000
因为出现了多线程,出现了丢失写值得情况
比如线程1,2,3都从主物理内存中拿到了num初始值0,在各自得工作内存中,各自自增1, 三个线程都要向主物理内存中写,1线程写入主内存,2,3线程挂起,然后num得值变为1,通知2,3,进程num值已经为1了。但是因为线程非常快,线程2或者3又可能再次把1又写入了主内存,写覆盖,造成了写数据丢失。所以最后得值就很大可能低于20000.恰好等于20000的几率极小。
number++在多线程下是非线程安全的,加synchronized,就能保证是线程安全了
package automitic;
public class Mydata {
volatile int num=0;
public void addto60(){
this.num=60;
}
public synchronized void addplusplus(){
num++;
}
}
加了之后就计算都是20000了
但是使用synchronized,有点大材小用
(3)不使用synchronized,volatile如何解决原子性
//使用JUP下的AtomicInteger
AtomicInteger atomicInteger= new AtomicInteger();//初始值为0
package Atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class Mydata {
//基本类型
volatile int num=0;
public void addto60(){
this.num=60;
}
public void addplusplus(){
num++;
}
//使用带原子性的num++
AtomicInteger atomicInteger= new AtomicInteger();//初始值为0
public void addAtomic(){
atomicInteger.getAndIncrement(); //增1
}
}
package Atomic;
//验证volatile不保证原子性
public class VolatileDemo {
public static void main(String[] args) {
Mydata mydata=new Mydata(); //资源类
//创建20个线程,每个线程操作num++ 1000次
for(int i=0; i<20;i++){
new Thread(()->{
for(int j=0;j<1000;j++)
// mydata.addplusplus();
mydata.addmyAtomic();
},String.valueOf(i)).start();
}
//等待20个线程执行结束后,用main线程取值,如果volatile保证原子性,那应该会得到值20000
while (Thread.activeCount()>2){ //这里为什么用2,因为默认后台就会有两个线程,main线程和GC线程
Thread.yield();
}
//控制多线程结束后再..的最好方法
// System.out.println(Thread.currentThread().getName()+"\t任务完成,int类型 num值现在为:"+mydata.num);
System.out.println(Thread.currentThread().getName()+"\t任务完成,AtomicInterger类型 值在为:"+mydata.atomicInteger);
}
}
运行结果,就一直是20000
本节新学知识点
1.java后台默认的两个线程 :main, GC
2.主线程main等待其他线程结束后再执行的最佳方法
while (Thread.activeCount()>2){ //这里为什么用2,因为默认后台就会有两个线程,main线程和GC线程
Thread.yield(); //控制多线程结束后再..的最好方法
}
- Thread.yield();
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
PS:学习笔记,尚硅谷,周阳大佬