Java多线程:线程间通信之volatile与sychronized

由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信。

Java为线程间通信提供了三个相关的关键字volatile, synchronized和final。对于final,我们在博文Java中static关键字和final关键字中已经介绍。

1. volatile

1.1. 定义

由volatile定义的变量其特殊性在于:

一个线程对变量的写一定对之后对这个变量的读的线程可见。

换言之

一个线程对volatile变量的读一定能看见它之前最后一个线程对这个变量的写。

1.2. 机理

volatile意味着可见性,在讲解volatile的机理前,我先给下面的这个例子:

package com.cielo.main;

/**
 * Created by 63289 on 2017/3/31.
 */
class MyThread extends Thread {
    private boolean isRunning = true;
    public boolean isRunning() {
        return isRunning;
    }
    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
    @Override
    public void run() {
        System.out.println("进入到run方法中了");
        while (isRunning == true) {
        }
        System.out.println("线程执行完成了");
    }
}
public class RunThread{
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,主线程启动了子线程,子线程成功进入run方法,输出"进入到run方法中",只有由于isRunning==true,无限循环。此时,sleep一秒后的主线程想要改变isRunning的值,它将isRunning变量读取到它的内存空间进行修改后,写入主内存,但由于子线程一直在私有栈中读取isRunning变量,没有在主内存中读取isRunning变量,因此不会退出循环。

如果我们把isRunning赋值行改为:

private volatile boolean isRunning = true;
将其用volatile修饰,则强制该变量从主内存中读取。

这样我们也就明白了volatile的实现机理,即:

  1. 当一个线程要使用volatile变量时,它会直接从主内存中读取,而不使用自己工作内存中的副本。

  2. 当一个线程对一个volatile变量写时,它会将变量的值刷新到共享内存(主内存)中。

1.3. 特性:不会被重排序

从Java内存模型一篇中,我们简单了解了重排序,这里不会被重排序主要指语句重排序。

我们考虑到下面这个例子,有A,B两个线程

线程A:加载配置文件,将配置元素初始化,之后标识初始化成功。

Map configOptions ;
char[] configText;

volatile boolean initialized = false;

//线程A首先从文件中读取配置信息,调用process...处理配置信息,处理完成了将initialized 设置为true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//负责将配置信息configOptions 成功初始化
initialized = true;
线程B:等待初始化标识为true,之后开始工作。

while(!initialized)
{
    sleep();
}

//使用配置信息干活
doSomethingWithConfig();

很简单的一个例子,在编译器中,如果进行重排序,则会有将initialized=true这一行先执行的可能,如果这件事发生的话,线程B就会先运行,进而使用了没有加载配置文件的Object。而如果initialized变量使用了volatile修饰,则编译器不会将该变量的相关代码进行重排序。(当然,这里的例子只是为了直观,实际情况编译器的重排序会更加复杂)

1.4. 非原子性

使用volatile时,我们要清楚,volatile是非原子性的。

原子性即是指,对于一个操作,其操作的内容只有全部执行/全不执行两个状态,不存在中间态。而volatile并不能锁定某组操作,防止其他线程的干扰,即没有规定原子性,因而volatile是非原子性的。或者说,volatile是非线程安全的。

综上,如果我们想要使用一个原子性的修饰符来控制操作,即在操作变量时锁定变量,我们就需要另一个修饰词synchronized。

2. synchronized

2.1. 定义

synchronized作用的代码范围对于不同线程是互斥的,并且线程在释放锁的时候会将共享变量的值刷新到共享内存中。

2.2. synchronized与voliatile区别

  1. 使用:voliatile 用于修饰变量,synchronized可以修饰对象,类,方法,代码块,语句。

  2. 原子性:voliatile只保证变量的可见性,不能用于同步变量,即不保证原子性,多线程并发访问voliatile修饰的变量时也不会产生阻塞。synchronized是原子性的,只有锁定了变量的线程才能进入临界区,从而保证临界区的所有语句全部执行。多线程并发访问sychronized修饰的变量会产生阻塞。

  3. 机理:

当线程对volatile变量读时,会把工作内存中值置为无效。当线程对sychronized变量读时,会在该线程锁定变量时把工作内存中值置为无效。

当线程对voliatile变量写时,会把值刷新到主内存中。当线程对sychronized变量写时,会在变量解锁时把值刷新到主内存中。

2.3. 注意

  1. 无论synchronized加在方法上还是对象上,其修饰的都是对象,而不是方法或者某个代码块代码语句。

  2. 每个对象只有一个锁与之相关联。

  3. 实现同步需要很大的系统开销来做控制,不要做无谓的锁定。

2.4. synchronized的作用域

synchronized的作用域只有两种。实际上,synchronized直接作用于内存中的一个内存块,因此,可以通过锁定内存块来锁定一个实例变量或者锁定一个静态区域。

  1. 某个对象实例内

synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法,如果对象有多个synchronized方法,则只要一个线程访问了任何一个synchronized方法,其他线程不能同时访问任何一个该对象的synchronized方法(synchronized作用于对象,且每个对象只有一个锁)。

显然,不同对象的synchronized方法则不会互相影响(synchronized作用于对象)。

  1. 某个类的范围

又或者说作用于静态方法/静态代码块。synchronized static aMethod(){}防止多个线程同时访问这个类中的synchronized static方法,它可以对类的所有实例对象起作用。

2.5. synchronized应用

2.5.1. synchronized方法

每个实例对应一个lock,线程获得该含有synchronized方法的实例的锁才可以执行,否则阻塞。方法一旦执行,则一直到方法返回才可以释放锁。此后被阻塞的线程才能获得该锁。对于一个实例,其声明为synchronized的方法显然只有一个能处于执行状态。从而避免了类访问变量的冲突。

synchronized同步的开销很大,如果synchronized作用于一个比较大的方法上,显然是不合算的。

2.5.2. synchronized代码块

synchronized代码块形式如下:

        synchronized (synchronizedObject){
            //Some thing
        }

代码块内部代码必须在获得synchronizedObject的锁时才能执行。需要重点说的是synchronized(this),这也是比较常用的代码块。

synchronized的效果类似于在方法前修饰,只是修饰的范围缩小成代码块。两个线程同时访问一个变量时,如果一个线程在执行synchronized的代码,那么该实例被锁定,另一个线程如果要访问该实例被synchronized作用的范围,则会被阻塞。

此外,如果不使用this作为锁,而是只是想让一段代码同步,可以临时创建如下锁:

    private byte[] lock=new byte[0];

从操作码上讲,创建一个长度为0的数组对象是最经济的,只需要3条操作码。

2.5.3. synchronized静态方法

synchronized修饰静态方法时或者在普通方法中以类为对象如下形式:

class StaticSynchronized{
    public void aMethod{
        synchronized (StaticSynchronized.class){
            //Some thing
        }
    }
}

为synchronized静态方法。

注意的是,对于同一个类,其static和实例方法如果都用synchronized修饰,其作用的必然不是同一个对象(显然)。

2.5.4. synchronized对象

比较简单粗暴的实现方式,直接把对象锁定,思路也很清晰。Java负责跟踪被加锁的对象,该锁定对象的线程每次给对象加锁时对象的计数器+1,每次解锁时计数器-1,如果对象的计数器为0,那么解除该线程的锁定。

3. 参考文章

如何使用 volatile, synchronized, final 进行线程间通信

JAVA多线程之volatile 与 synchronized 的比较

Java synchronized详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,014评论 11 349
  • 前言 今天介绍下volatile关键字,volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java ...
    嘟爷MD阅读 1,252评论 7 27
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,633评论 0 11
  • 早上看到周口淮阳一中,一同学与同学发生口角被罚站,从六楼跳下身亡。 我不知道他跳楼之前究竟发生了什么,但他的死亡是...
    菏叶母亲阅读 383评论 0 3
  • 现在发现,只要有心,一句话、一件事、一篇文章、一部电影都能让自己学习和成长。有时候,不经意间,电影的一句旁白也...
    妮子还在阅读 117评论 1 1