java 线程1 线程安全(浅谈 synchronized 和 volatile 区别)

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并不能保证线程安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值