java中关键字volatile的作用; volatile vs synchronized的区别

/**
 * volatile
 * Created by Lovell on 8/3/16.
 */
public class StoppableTask {

    /**
     * 用在多线程,同步变量。线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。
     * 只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。
     * volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A)
     * 在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。
     * 为了性能,一个线程会在自己的memory中保持要访问的变量的副本。
     * 这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,
     * 或者main memory中的值不一致的情况。
     * 一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。
     * 以下例子展现了volatile的作用:
     */
    private volatile  boolean pleaseStop;

    public void run() {
        while (!pleaseStop) {

            // do some stuff...
            System.out.println("run in");
        }
    }

    public void tellMeToStop() {
        pleaseStop = true;
    }
    /**
     * 假如pleaseStop没有被声明为volatile,线程执行run的时候检查的是自己的副本,
     * 就不能及时得知其他线程已经调用tellMeToStop()修改了pleaseStop的值。
     */
}


/**
 * volatile vs synchronized
 * Created by Lovell on 8/3/16.
 */

public class volatiletest1 extends Thread {
    /**
     * Volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性,
     * 即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,
     * volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。
     * 如果配合Java 5增加的atomic wrapper classes,对它们的increase之类的操作就不需要sychronized。
     *
     * volatile是变量修饰符,而synchronized则作用于一段代码或方法
     * Reference:
     * http://www.javamex.com/tutorials/synchronization_volatile.shtml
     * http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml
     * http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
     */
    int i1;

    int geti1() {
        return i1;
    }

    volatile int i2;

    int geti2() {
        return i2;
    }

    int i3;

    synchronized int geti3() {
        return i3;
    }
    /**
     * geti1()得到存储在当前线程中i1的数值。多个线程有多个i1变量拷贝,而且这些i1之间可以互不相同。
     * 换句话说,另一个线程可能已经改变了它线 程内的i1值,而这个值可以和当前线程中的i1值不相同。
     * 事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程可以有它 自己的变量拷贝,
     * 而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,
     * 线程1里的i1值是2,线 程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,
     * 而且这个改变还没来得及传递给“主”内存区域或其他线程时就会发生。
     * 而geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝。
     * 换句话说,一个变量经 volatile修饰后在所有线程中必须是同步的;
     * 任何线程中改变了它的值,所有其他线程立即获取到了相同的值。
     * 理所当然的,volatile修饰的变量 存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。
     * 既然volatile关键字已经实现了线程间数据同步,又要 synchronized干什么呢?呵呵,它们之间有两点不同。首
     * 先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监 视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。
     * 但是,synchronized也同步内存:事实上,synchronized在“ 主”内存区域同步整个线程的内存。
     * 因此,执行geti3()方法做了如下几步:
     * 1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)
     * 2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。)
     * 3. 代码块被执行
     * 4. 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)
     * 5. 线程释放监视this对象的对象锁
     * 因此volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。
     * 显然synchronized要比volatile消耗更多资源。
     */

}

/**
 * volatile关键字相信了解Java多线程的读者都很清楚它的作用。volatile关键字用于声明简单类型变量,如int、float、 boolean等数据类型。
 * 如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。
 * 例如,下面的例子中的n就 不是原子级别的:
 */
class volatiletest2 extends Thread {
    public static volatile int n = 0;

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                n++; // n = n + 1 同样不能保证是原子操作
                sleep(3); //  为了使运行结果更随机,延迟3毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            // 建立100个线程
            threads[i] = new volatiletest2();
        }

        for (Thread thread : threads) {
            // 运行刚才建立的100个线程
            thread.start();
        }

        for (Thread thread : threads) {
            // 100个吸纳成都执行完后继续
            try {
                thread.join();
                System.out.println("n = " + volatiletest2.n);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,
     * 很多时侯输出的n都小于1000,这说明n=n+1不是原子级别 的操作。
     * 原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,
     * 那么volatile关键字不起作用,也就是说如下的表达式都不是原子 操作:
     * n++;
     * n = n+1;
     */
}

class volatiletest3 extends Thread {
    public static int n = 0;

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

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                inc(); // n++搞成了intc();
                sleep(3); // 为了使运行结果更随机,延迟3秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            // 建立100个线程
            threads[i] = new volatiletest3();
        }

        for (Thread thread : threads) {
            // 运行刚才建立的100个线程
            thread.start();
        }

        for (Thread thread : threads) {
            // 100个吸纳成都执行完后继续
            try {
                thread.join();
                System.out.println("n = " + volatiletest3.n);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。
     * 因此,在使用volatile关键字时要慎 重,并不是只要简单类型变量使用volatile修饰,
     * 对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++ 等,
     * 如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,
     * 可以使用synchronized来代替volatile。
     */
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VCHH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值