Java 线程安全1(浅谈 synchronized 和 volatile 区别)
背景信息
原子性
原子性的操作是不可分解的。
比如 a=0 这个操作是不可分解的
再比如:a++; 这个操作实际是a = a + 1;是可分解的
JVM的内存模型
JVM运行中,有两块主要的内存:
主内存:真实的电脑内存
每个线程都具有的工作内存:可以理解为JVM 给每个线程创建的虚拟内存(但是实际上也占用了部分真实内存地址)
这样做的好处,就是虚拟机的好处:让jJava语言编译程序只需生成在Java虚拟机上运行的代码,就可以在多种平台上不加修改地运行,让软件做软件的事情,其他的事情交给虚拟机和硬件沟通。
首先 模拟迪士尼卖票程序
Disney类
public class Disney {
//记录总的票数
public int tickets =200;
public Disney(){
}
}
User 类
public class User implements Runnable{
private Disney disney;
public User(Disney disney){
this.disney =disney;
}
//创建新的线程
@Override
public void run() {
for (int i=0; i< 50;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟买票
System.out.println(Thread.currentThread().getName()+"得到了第 "+disney.tickets +"号票");
this.disney.tickets --;
}
}
主程序类
public static void main(String[] args) {
Disney disney =new Disney();
User user =new User(disney);
new Thread(user).start();
new Thread(user).start();
new Thread(user).start();
new Thread(user).start();
}
**这时候会遇到问题:不同的用户买到了同一张票,不同线程 **
这就是 简单的线程安全问题
那么怎么解决这个问题呢?这个时候就会用到 synchronized 这个关键字了
synchronized 关键字
synchronized 可以让方法或者代码块变成原子性操作,这是保证线程安全的一种方法
当一个线程访问synchronized(this)的同步代码块时
其他线程对这个代码块的访问将被阻塞。
方法
public synchronized void lock(){
System.out.println(Thread.currentThread().getName()+"得到了第 "+disney.tickets +"号票");
this.disney.tickets --;
}
//创建新的线程
public void run() {
for (int i=0; i< 50;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock();
}
}
代码块
//创建新的线程
@Override
public void run() {
for (int i=0; i< 50;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 方法1: 同步代码块
synchronized (this){ //这部分必须是唯一的1个对象
System.out.println(Thread.currentThread().getName()+"得到了第 "+disney.tickets +"号票");
//它不是原子操作
this.disney.tickets --;
}
}
}
这里使用synchronized, 要输入一个参数,需要一个在程序中 唯一的对象(即监视器)。
在User类中,使用了this方法,因为在这个程序中。
User 实现了Runnable接口, 用User 创建新的线程,User本身只有一个。
如果User 继承了Thread 类,则不能用this作为参数。
运行结果
volatile关键字
volatile 用法
volatile 原理
volatile 只能保证可见性,不能保证原子性
volatile 修饰变量,不能保证线程安全
回到JVM线程模型
当某个线程写入一个volatile修饰的变量时,JVM会把 对应的工作内存中的变量,强制刷新到计算机的真实内存中去,可以理解为其他线程都知道了这个消息
举个例子:
如果 线程1把变量 volatile修饰的变量A 改成5
那么其他线程 2,3,4,5 都会知道 A =5
线程 1: 写入A=5
线程 2:知道A=5 读取 A=5,正在写入 A=6 的过程中
线程3: 知道A=5 读取 A=5,线程安全问题出现
所以volatile并不能保证线程安全。