Volatile关键字简述

前言

最近在看《Java并发编程实战》,期望对一些并发的知识点做一些总结。最好有一定的Java基础、并发的基础。

前置知识

程序、进程、线程

程序

程序是静态的代码块。

进程

进程是动态的,是程序运行之后的产物,同时进程是资源分配的最小单元。

线程

线程是对进程进一步细化,因为CPU的数量限制了进程的数目,为了适应更大的吞吐量,线程就产生了。同属于一个进程的线程共享该进程的资源,是操作系统进行调度的最小单元。

并发所涉及的一些特性

线程安全

多个线程同时访问某个资源的时候,不需要额外同步,都能保证结果的正确性。

原子性

代码块运行期间不会受到外部的影响,即该代码块是不可分割的最小的操作单元。

可见性

某个线程修改了共享的数据单元之后,能够立即通知到其他线程。

Volatile

相比synchronized(重量级同步),Volatile是轻量级同步锁,方便了并发编程,常用在信号量来控制程序执行逻辑,但是过度依赖volatile变量会使得使用锁的代码更脆弱,难以理解。

相比于synchronized,Volatile读取的时候不会加锁,所以不能保证线程安全。

Volatile能够保证可见性,无法保证原子性,参见下面的例子。

案例

环境

maven3.X、JDK17

代码展示

可见性测试

没有volatile,这时候如果自增发生在while循环取得num之后,就会导致死循环。

    // 可能死循环
    static int num1 = 1;
    
    /**
     * 可能死循环
     */
    @Test
    public void atomTest1() {
        new Thread (VolatileTest::addNumByOne1).start ( );
        while (num1 < 2) {
        }
        System.out.println (num1);
    }

    static void addNumByOne1() {
        try {
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            throw new RuntimeException (e);
        }
        num1++;
    }

通过Volatile保证了无论何时在其他线程修改num之后,主线程能够正确的读取修改之后的num值,以跳出循环

    // 可见性,一旦修改 就通知
    static volatile int num2 = 1;

    /**
     * 可见性体现
     */
    @Test
    public void atomTest2() {
        new Thread (VolatileTest::addNumByOne2).start ( );
        while (num2 < 2) {
        }
        System.out.println (num2);
    }
    
	static void addNumByOne2() {
	       try {
	           TimeUnit.SECONDS.sleep (1);
	       } catch (InterruptedException e) {
	           throw new RuntimeException (e);
	       }
	       num2++;
	   }

原子性测试

这里很容易以为最后打印出的结果是20000,因为volatile保证了可见性,那么每个线程都能获取到其他县城修改之后的num,进行+1。但是结果大多数都是小于20000的。

    // 原子性
    static volatile int num3 = 1;
     
     /**
     * 不能保证原子性
     */
    @Test
    public void atomTest3() throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool (10);
        for (int i = 0; i < 10; i++) {
            pool.submit (new Thread (() -> {
                for (int j = 0; j < 2000; j++) {
                    addNumByOne3();
                }
            },"t" + i));
        }
        pool.shutdown ();
        pool.awaitTermination (1,TimeUnit.SECONDS);
        System.out.println (num3);
    }

    static void addNumByOne3() {
        num3++;
    }

运行结果:
在这里插入图片描述

产生这个结果的原因就是,当某个线程获取到了num3,准备自增的时候,别的线程也读取到了这个num3, 也发生了自增,最后写入内存,那么本来应该增加了2,现在可能只增加了1。具体点说当前num3更新到了3,这时候t1读取到了num3准备更新的时候并不会阻止t2来读取num3,这时候两个num3处于相同的数值3,之后t1,t2各自执行num3+1,写回内存,那么自然最后更新的结果就出现了问题了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值