android

Java基础-浅谈关键字volatile和synchronize

2019-03-05  本文已影响50人  九心_

前言

这几天一直在学习Java并发相关的知识(比刚上大学那会儿看好多了,哈~),所以觉得有必要做一些知识总结。当我们在谈到并发或者查看一些开源库的源码的时候,我们会经常看到关键字volatilesynchronized,如果不了解这些关键字,其实还是挺头疼的。

目录

目录

一. 定义

volatilesynchronized是Java中的关键字

二. 作用

我们都知道,Java中某个线程在第一次读取共享属性之前,会从主内存中复制这个属性到线程的工作内存中,之后才会操作这个共享属性,在操作完成之后,如果共享属性的值发生了修改,则会先保存到本地的工作线程,然后才会刷新到主内存(貌似跟作用没什么关系,不过需要理解)。此外,在正式介绍这两个关键字作用之前,我觉得有必要了解三个名词:原子性、可视性和有序性

名词 解释
原子性 一个操作/一系列操作要么全部执行要么都不执行
可视性 如果一个线程对一个共享值作出了一些修改,其他线程都可以看到这个共享值的修改。
有序性 程序的运行顺序要和程序的逻辑顺序一致,可能实际情况是计算机考虑到性能的因素执行顺序有所不同,但是结果肯定会跟逻辑顺序一致。

下面来介绍我们今天的主角:
1. volatile
对于volatile的作用,《Java核心技术卷》是这么说的:

实例域的访问提供了一种免锁机制,不提供原子性

首先,实例域说明它是用来修饰域的,如下:

private volatile boolean isNum = false;

其次,对实例域的访问提供了免锁机制说明volatile具有可视性;最后的重点就是没有原子性。《Java编程思想》中提到:

所有的基础类型(除了doublelong)的基础操作(读取和写入)都具有原子性,经过volatile修饰的longdouble在基础操作中也会具有原子性。

所以volatile除了提供可视性之外,还能够为longdouble的读取和写入提供原子性,此外,volatile还可以保证有序性。
2. synchronized
在Java中,所有实例对象都自动含有单一的锁(也称监视器)。所以,当使用synchronized修饰的方法的时候,该方法会自动给实例加锁,这个时候,其他含synchronized的方法必须等到该方法调用结束并释放锁之后,该方法才能够被调用。简单来说,就是同一时间内,只能有一个线程访问 synchronized修饰的方法或者代码块啊,保证了原子性、有序性和可视性。

三. 使用

1. volatile
volatile的使用比较简单:

// 对一个域加上volatile,域可以确保可视性和有序性
private volatile int b;

// 对long和double加上volatile,可以使如下的读取和写入具有原子性。
private volatile long a;

public long getA(){ return a;}
public void setA(A a){ this.a = a;}

2. synchronized
相对于volatile的使用,synchronzied的使用相对复杂一点:

public class Test {
    private int a;
    public static int b;

    public synchronized void addOne() {
        a = a + 1;
    }

    public void addTwo() {
        synchronized (this) {
            a = a + 2;
        }
    }

    public void addThree() {
        synchronized (Test.class) {
            b = b + 3;
        }
    }

    public synchronized static void addFour() {
        b = b + 4;
    }
}

根据以上代码,我们看到synchronized的使用通常会分两种类型:

值得注意的是,一个任务可以多次获得对象的锁,当在一个synchronized方法中调用同一个对象的另一个synchronized方法,该方法就会使得JYM跟踪的线程的持有的锁的数量加一,方法调用结束的时候,持有锁的数量就会减一,直到数字减为零,锁才会被释放。

四. 进一步探究

在这里,我准备向各位同学浅析一下volatilesynchronized的作用。
1.volatile
从第二部分中,我们了解到volatile能够保证有序性和可视性以及为doublelong的读取和写入操作的原子性,除此之外,不会保证其他操作的原子性。

只要对volatile域产生了写操作,那么所有的读操作都可以看到这个修改。即便使用了本地缓存,情况也确实如此,volatile域会被立即写入主存中,而读取操作就发生在主存中。

volatile int num = 1;
public void doAddOne() {
  num++;
  // 自增包括两个步骤:
  // 1. 读取num的值 2. num+1 写入修改后的值
  // 假设线程A在读取num值的时候,被挂起,这个时候线程B读取num的值
  // 线程B对num进行自增,结果为1
  // 这个时候,线程A恢复调度,对起初读取的num的值进行自增,结果也会1
  // 0经过两个自增,结果仍然为1,所以说volatile域并不能保证原子性
}

2. synchronized
这里,我们就简单的讨论一下synchronized的执行过程吧:
(1)获得同步锁。
(2)清空工作内存。
(3)从主内存中复制数据副本到工作内存。
(4)执行代码。
(5)刷新数据到主内存。
(6)释放锁。
这也就是synchronized能够具有可视性、有序性和原子性的原因。

五. 总结

总结

六. 拓展

其他解决并发的方式:

Java基础-浅析解决并发的几种方式

七. 引用

本人水平有限,难免会有错误,如有错误,欢迎各位同学指出~
如下是本人的参考:

  1. 书籍:《Java核心技术卷》《Java编程思想》
  2. 博客:
    Java:手把手教你全面学习神秘的Synchronized关键字
    Java:鲜为人知的关键字volatile
    java本地内存什么时候刷新到主内存中去
上一篇下一篇

猜你喜欢

热点阅读