JAVA并发编程(二)volatile与synchronized

在java多线程知识中有很多关键字,常用的就是volatile和synchronized两个,这两个关键字分别保证了字段数据的可见性临界区的同步性。这篇文章对所有关于此两个关键字的作用和用法作一个总结。

数据的可见性


什么叫做数据的可见性?

想这样一种情况,线程A对共享属性a进行修改之后,线程B读取属性a,但是读取到的是A修改之前的旧数据,发生了错误。这种错误的发生不是因为线程B和线程A并行造成的,线程A完成了所有操作之后,线程B读到的属性a仍然是修改前的旧数据。发生这种问题是因为我们没有保证属性a具有良好的可见性。

为什么会发生可见性问题?

听了上面的例子你可能会感觉很魔幻,你会反驳我:“这种情况根本不可能出现”。诚然我们平时的小程序很少出现这种情况,但是不能确保一个庞大的系统不会发生这种问题,而且这种问题的出现不是我们主观臆断的,是有存在的理由的。

如果你熟悉JVM的运行时数据区,你会记得在JVM的内存堆中有这样一块区域叫做TLAB,全程是Thread Local Allocation Buffer即多线程私有分配缓冲区。这个区域分配给各个线程作为私有存储空间,当线程创建对象需要分配内存时直接在TLAB中给出空间,这样的好处在于会更快速,因为堆中的数据存放是很复杂的,TLAB能够解决分配速度的问题。但是可见性的问题也出现了,各个线程都需要使用的数据存放在公共的堆空间中,线程需要读取数据a时到公共堆空间中去读取数据a,把a读取到自己的缓冲区中进行处理,当线程将a读取到缓冲区中时,有可能公共空间中的a已经发生了变化,这样导致线程读到的数据是旧数据。

volatile


volatile能做什么?

为了解决这个问题volatile应运而生,volatile使得我们读取数据时直接到公共空间的读取最新的数据,并直接将数据写入到自己的线程的程序中,省去了中间的缓冲区的部分,用这种方法确保数据的可见性。当一个属性被volatile修饰时,我们也就说:这个属性具有可见性,对于任何线程操作都能保证获取这个属性是最新值。

volatile不能保证操作的原子性

我们解释了可见性的概念,但这和原子性不同。原子性主要指于多步操作不会被其他线程打断或干扰的操作。比如i++,自增的操作分成三个步骤。实际上一个线程在执行这三个步骤的时候,是可能被其他线程干扰的,所以i++不是原子性操作。我们不能用volatile修饰i来希望i++变为原子操作。这根本不是一个概念。

volatile只能保证那些关于它修饰的属性的操作不会被重排序,保持此属性的可见性。这是一种轻量级的同步概念,但是volatile能做的太少了,实际上我们也不必为每个处于多线程共享的属性都加上volatile,对于那些我们必须要保证可见性的操作而言volatile是需要的,比如:

package Main;

/**
 * Title: NoVisibility
 * Description:
 * Company: www.QuinnNorris.com
 *
 * @date: 2018/1/24 下午5:32 星期三
 * @author: quinn_norris
 * @version: 1.0
 */
public class NoVisibility {

    private volatile static boolean flag;
    private static int number;

    public static void main(String[] args) {
        new SubThread().start();
        number = 11;
        flag = true;
    }

    private static class SubThread extends Thread {

        @Override
        public void run() {
            while (!flag)
                Thread.yield();
            System.out.println(number);
        }
    }
}

这段代码中flag作为条件判断的属性就有必要加上volatile修饰,保证当它发生变化的时候能够在其他线程及时跳出while循环。

synchronized


java是一门面向对象的编程语言,所以这样说起来synchronized看起来就有些奇怪,因为他是加在代码段上使用的,这就给我们一种面向过程编程的感觉,实际上并不是这样的,synchronized的使用方法也有很多说法在里面。

synchronized一共有四种用法:

  1. 修饰代码段
  2. 修饰方法
  3. 修饰静态方法
  4. 修饰类

synchronized修饰代码段

synchronized修饰代码段时表示在代码段内部的部分是临界区,一次只能被同一个类的一个线程访问

/**
 * Title: SyncThread
 * Description:
 * Company: www.QuinnNorris.com
 *
 * @date: 2018/1/24 下午5:32 星期三
 * @author: quinn_norris
 * @version: 1.0
 */
class SyncThread implements Runnable {
    private static int count;

    public void run() {
        synchronized (this) {
            //todo
        }
    }

}

上面的代码中,todo的内容在同一时间在同样的一个对象中只能被一个线程执行。请注意,上面的条件很重要,同一时间是指在一个时间点上,同一对象是指不同的线程用同一个SyncThread对象进行运行时才会起作用,如果不同的线程创建了不同的SyncThread对象,那么syncrhonized并不会起到作用。

synchronized修饰方法

修饰方法和修饰代码段很类似,就是将关键字写在方法名前,并且临界区是整个方法,使用这种方法虽然简单方便,但是需要注意,这样写让整个方法都变成临界区很可能会让效率急剧降低,你的并行程序会慢慢变成“串行程序”,这是我们不愿见到的。

/**
 * Title: SyncThread
 * Description:
 * Company: www.QuinnNorris.com
 *
 * @date: 2018/1/24 下午5:32 星期三
 * @author: quinn_norris
 * @version: 1.0
 */
class SyncThread implements Runnable {
    private static int count;

    public synchronized void run() {
        //todo
    }

}

synchronized修饰静态方法

/**
 * Title: SyncThread
 * Description:
 * Company: www.QuinnNorris.com
 *
 * @date: 2018/1/24 下午5:32 星期三
 * @author: quinn_norris
 * @version: 1.0
 */
class SyncThread implements Runnable {
    private static int count;

    public synchronized static void abs() {
        //todo
    }

    @Override
    public void run() {

    }
}

修饰静态的方法和修饰方法类似,只不过锁的对象是所有此类创建的对象。

synchronized修饰类

修饰类和修饰静态方法是完全一样的,只不过换了一种写法,修饰类和修饰静态方法的关系可以看作是,修饰代码段和修饰方法之间的关系。只不过前两者是锁类的所有的对象,而后两者是锁一个实例化的对象。

/**
 * Title: SyncThread
 * Description:
 * Company: www.QuinnNorris.com
 *
 * @date: 2018/1/24 下午5:32 星期三
 * @author: quinn_norris
 * @version: 1.0
 */
class SyncThread implements Runnable {
    private static int count;

    public void method() {
        synchronized(SyncThread.class) {
            // todo
        }
    }
    @Override
    public void run() {

    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值