Java并发同步之synchronized与volatile

多线程环境下,共享变量同步的方式有很多种,本文主要对synchronizedvolatile两个修饰符作个比较,总结如下:

* volatile 仅能使用在域级别;synchronized可以使用在变量、方法、类级别。

* volatile 不执行互斥访问,不会造成线程阻塞;synchronized 会造成线程阻塞。

* volatile 修饰变量具有synchronized的可见性,但不具备其原子特性。

* volatile 本质上是告诉JVM当前变量在工作内存中的值是不确定的,需要从主存中读取;synchronized本质是当线程锁定变量时,阻止其他线程访问此变量。

使用 volatile变量提供线程安全,必须满足下面两个条件:

 * 对变量的写操作不依赖于当前值。比如`i++`这种操作是不允许的。

 * 对变量没有包含在具有其他变量的不变式中。

详细可以看以下例子(选自Effective Java):

并发场景:一个线程阻止另一个线程执行任务。Java类库中提供了Thread.shop()方法停止其他线程,但这个方法早已不提倡使用,因为它是线程不安全的unsafe,使用它会导致数据遭到破坏。

解决此问题,可以让第一个线程轮询poll一个公共的boolean域,这个域初始值为false,第二个线程可以通过设置此域为true来终止第一个线程。


public class ThreadTest {

    private static boolean stop;



    public static void main(String[] args) {

        Thread bgtr = new Thread(new Runnable() {

            @Override

            public void run() {

                long s1 = System.currentTimeMillis();

                int i = 0;

                while (!stop) {

                    i++;

                }

                long s2 = System.currentTimeMillis();

                System.out.println("time:" + (s2 - s1));

            }

        });

        bgtr.start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        stop = true;

    }

}

分析这段代码,你可能认为此程序大约运行1分钟,而实验表明,大多数情况下,这个程序都永远不会终止,因为后台线程bgtr永远在循环。

问题在于,由于没有同步,就不能保证后台线程何时“看到”主线程对stop变量的修改,此时JVM将这代码:


while(!done)

    i++;

等价于


if(!done)

    while(true)

        i++;

解决此问题大部份程序员想到的是加synchronized关键字,同步访问stop域,代码如下:


public class ThreadTest {

    private static boolean stop;

    private static synchronized void requestStop() {

        stop = true;

    }

    private static synchronized boolean stopRequested() {

        return stop;

    }



    public static void main(String[] args) {

        Thread bgtr = new Thread(new Runnable() {

            @Override

            public void run() {

                long s1 = System.currentTimeMillis();

                int i = 0;

                while (!stopRequested()) {

                    i++;

                }

                long s2 = System.currentTimeMillis();

                System.out.println("time:" + (s2 - s1));

            }

        });

        bgtr.start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        requestStop();

    }

}

加了同步访问方法后,运行时间约为1s,达到了预期效果,但是本例的目的是为了两个线珵之间可以通信,并不是为了互斥访问,使用synchroinzed关键字会造成线程阻塞,性能上有一定损失。此场景下,替代的方法是使用volatile关键字修饰变量,同步锁代码可以删除。volatile修饰符不执行互斥访问,但它可以保证任何一个纯种在读取该域时都能看到它的最新值。


public class ThreadTest {

    private static volatile boolean stop;



    public static void main(String[] args) {

        Thread bgtr = new Thread(new Runnable() {

            @Override

            public void run() {

                long s1 = System.currentTimeMillis();

                int i = 0;

                while (!stop) {

                    i++;

                }

                long s2 = System.currentTimeMillis();

                System.out.println("time:" + (s2 - s1));

            }

        });

        bgtr.start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        stop = true;

    }

}

效果等同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值