Volatile的学习(包含线程安全的单例)

1.问题引入  多线程同时对一个变量进行操作

public class MyThread extends Thread {
    private static int n = 0;

    public void run() {
        for (int i = 0; i < 10; i++) {
          n++;
            try {
                sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Thread threads[] = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new MyThread();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join(); //等待该线程终止
        }
        System.out.println(" n= " + n);
    }
}
运行结果:n= 991
原因: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。简单的说就是不同线程不能及时的看到同一个变量

2.volatile引入:

volatile 的作用,简单说就是在java中一条语句,在多线程条件下,在我们看来要么被执行完成,要么没被执行;可现实情况是,编译器可能把这条语句翻译成多条执行语句,多线程下,可能出现这条java语句被执行一半后又被切到另外的线程去执行的情况。而这会导致一些未知的问题,所以Java引入了volatile关键字,保证一条执行语句执行过程的原子性:要么被执行完毕,要么没被执行。

例子修改:
public class MyThread extends Thread {
    private static volatile int n = 0;

    public void run() {
        for (int i = 0; i < 10; i++) {
            n++;
            try {
                sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Thread threads[] = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new MyThread();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join(); //等待该线程终止
        }
        System.out.println(" n= " + n);
    }
}
运行结果: n = 981
原因:Volatile关键字用于声明简单类型变量,如int、float、 boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制,表达式也应该要保证是原子操作,比如这个例子中的n++就不是原子操作

改进:
public class MyThread extends Thread {
    private static volatile int n = 0;

    public void run() {
        for (int i = 0; i < 10; i++) {
            add();
            try {
                sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static synchronized void add() {
        n++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread threads[] = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new MyThread();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join(); //等待该线程终止
        }
        System.out.println(" n= " + n);
    }
}
运行结果: n = 1000

      上面的代码将n++改成了add(),其中add方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作, 当变量的值由自身的上一个决定时,如n=n+1、n++ 等,volatile关键字将失效 ,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile
    这里可以看下  synchronized相关理解

3.volatile的常见使用场合

单例:
 private static volatile Singleton instance;

    private Singleton(){
    }

    public static  Singleton getInstance(){

        if(instance==null){
            synchronized(Singleton.class){
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }  
	
状态标记量:
 volatile boolean flag = false;

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

    public void fun(){
        while (!flag)
            doSomething();
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值