Java多线程大闯关--volatile关键字

        这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、并发等相关概念的理解,创建多线程的几种方式,Thread类,线程的同步机制,线程通信等几个大的模块。该系列博客的文章如下,推荐按顺序阅读:

Java对于多线程的安全问题提供了专业的解决方式:同步机制。对多条操作共共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。前面所说的synchronized就是一把锁,多个线程抢这把锁,谁抢到了谁就上,其它的线程只能等那个线程爽完了把锁释放了才能再抢一次,实际上这抢来抢去的不免失了和气,而且频繁的线程上下文切换和调度增加了使用和执行成本。volatile是轻量级的volatile,在多处理器开发中保证共享变量的“可见性”,意思就是CPU1中的某个线程把某个用volatile修饰了的变量修改了,其他CPU中的线程都能看读到这个被修改后的值。

volatile的定义与实现原理

程序在运行过程中,会将运算需要的数据从物理内存中复制一份到 CPU 的高速缓存当中,计算结束之后,再将高速缓存中的数据刷新到物理内存当中。

  • 定义:Java编程语言允许线程访问共享变量,为了确保共享变量能够准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
  • 原理:volatile变量修饰的共享变量进行写操作时,当前处理器缓存行的数据会被写回到系统内存,这个写回内存的操作会使得其他在CPU里缓存了该内存地址的数据无效,当处理器对这个数据进行修改时,会重新从系统内存中把数据读到缓存里。

还是通过代码来直观的看一下volatile的效果,依然是三个窗口卖票的问题。

package com.learnjiawa.mutithread;

/**
 * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
 * 存在线程的安全问题,待解决。
 */
class Window extends Thread{


    private static int ticket = 100;
    @Override
    public void run() {

        while(true){

            if(ticket > 0){
                //睡一会,增加线程安全问题出现的机率
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + ":卖票,票号为:" + ticket);
                ticket--;
            }else{
                break;
            }

        }

    }
}


public class WindowTest {
    public static void main(String[] args) {
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

使用volatile前
将ticket关键字用volatile修饰一哈,再运行

 private static volatile int ticket = 100;

你以为这就可以保证线程安全了吗,too young too simple,volatile关键字只能保证变量的可见性,不能保证操作的原子性,比如A线程修改了该共享变量的值,之后B线程读取到的了A线程修改后的值,但是这时候这CPU没有选择紧接着执行B线程的写操作,而是安排C线程也读到了该值,这使得两个线程都已经完成了从系统内存中读取到缓存中的操作,然后分别直接进行了对该变量的写操作,出现了重票现象。所以必须将这个读写操作变成原子性的操作(即一个操作或者多个操作要么全部执行,并且执行的过程不会被任何因素打断,要么就都不执行)。
代码更改如下:

package com.learnjiawa.jihe;

import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
 *
 * 存在线程的安全问题,待解决。
 */
class Window extends Thread{

    private static AtomicInteger ticket = new AtomicInteger(100);
    
    @Override
    public void run() {

        while(true){

            if(ticket.get() > 0){
                //睡一会,增加线程安全问题出现的机率
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + ":卖票,票号为:" + ticket.getAndDecrement());

            }else{
                break;
            }

        }

    }
}


public class WindowTest {
    public static void main(String[] args) {
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

改为原子操作
哎哟,重票现象没有了,但是还有个小尾巴,在最后的时候,有一个线程前一个线程睡觉的时候悄悄跑了进去,等到前一个线程输出后,它也醒了,但这时它读到了前一个线程修改过后的值“0”,但是这个线程已经过了门禁了,所以输出了个-1。不过这里总算把线程安全问题的重票问题解决了,这个小尾巴解决起来应该简单,就不搞啦,这里主要是了解一下volatile关键字和原子操作类,哦,忘了说,原子操作类源码里面是依赖了volatile关键字的,具体的底层原理等以后写并发专题的时候再写把,这里先简单了解。

参考文献

[1]Bruce Eckel.Java编程思想(第4版)[M].机械工业出版社,2008:650-757.

更多

对我的文章感兴趣,持续更新中…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值