堆栈花园的多线程学习备忘录——volatile关键字

一、volatile的作用
  • volatile关键作用是使变量在多个线程之间可见。
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Print print = new Print();
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    print.print();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //main方法睡一会
        Thread.sleep(1000);
        //修改打印标志flag
        print.setFlag(false);
    }
}

class Print{
    private volatile boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void print() throws InterruptedException {
        int i = 0;
        System.out.println(Thread.currentThread().getName()+"线程开始了...");
        while (flag){
            i++;
        }
        System.out.println(Thread.currentThread().getName()+"线程结束了...");
        System.out.println(i);
    }
}

以上代码的运行结果:
在这里插入图片描述
通过运行结果发现print()因为while循环的存在,进入了死循环,而在main方法中对循环的标志进行更改,也无法使while循环的结束,也就是子线程读不到flag的变化。

  • 解决方案:
    使用volatile修饰打印标志,这样可以强制线程从公共内存读取变量的值,而不是在自己的工作内存读取变量的值。
private volatile boolean flag = true;//解决上述问题

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

二、比较一下volatile和synchronized
  • volatile关键字和synchronize关键字都是java多线程知识中重要的内容,所以学习的时候,我们不妨将它们比较一下,分析出它们各自的特点与区别,加深我们的记忆。
  1. volatile 关键字是线程同步的轻量级实现,所以volatile的性能肯定比synchronize要好。
  2. volatile只能修饰变量,而synchronize可以修饰方法,代码块。
  3. 多线程访问volatile变量不会发生阻塞,而访问synchronize的方法或代码块,可能会发生阻塞。
  4. volatile可以保证数据的可见性,但不能保证原子性。而synchronize既可以保证可见性又可以保证原子性。
  5. 关键字volatile解决的是变量在多个线程之间的可见性。而synchronize解决多个线程之间访问公共资源的同步性。
三、volatile的非原子特性
  • volatile关键字增加了实例变量在多个线程之间的可见性,但是不具备原子性。
public class Demo3 {
    public static void main(String[] args) {
        //在main里创建十个子线程
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread()).start();
        }
    }
    static class MyThread implements Runnable {
        public static volatile int count = 0;
        public static void addCount(){
            for(int i = 0; i < 1000; i++){
                count++;
            }
            System.out.println(Thread.currentThread().getName()+","+count);
        }

        @Override
        public void run() {
            addCount();
        }
    }
}

运行结果:在这里插入图片描述
根据以上代码,正常情况下,count被十个线程各加了一千次,count应该变成10000,但结果不是。说明一个线程在for循环里还没加完,别的线程就进来抢夺了执行权。说明volatile仅仅表示所有线程从主内存读取count的值,count++不是一个原子操作,所以为了实现原子性,就需要加上synchronize进行同步。

public synchronized static void addCount()

在加上synchronized后,volatile也不需要了,因为synchronize既保证了原子性也保证了可见性。

四、常见原子类进行自增自减
  • i++和i–不是原子操作,除了使用synchronize进行同步外,也可以使用AtomicInteger/AtomicLong原子类进行操作。
public static void main(String[] args) {
        //在main里创建十个子线程
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread()).start();
        }
    }
    static class MyThread implements Runnable {
        private static AtomicInteger count = new AtomicInteger();
        public static void addCount(){
            for(int i = 0; i < 1000; i++){
                count.getAndIncrement();
            }
            System.out.println(Thread.currentThread().getName()+","+count);
        }

        @Override
        public void run() {
            addCount();
        }
    }

运行结果:在这里插入图片描述通过以上代码发现,即使没有使用volatile和synchronize关键字,通过AtomicInteger也实现了原子性.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值