Java 线程并发之synchronized和Volatile

每个线程都有自己的执行空间(即工作内存),线程执行的时候用到某变量,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作:读取,修改,赋值等,这些均在工作内存完成,操作完成后再将变量写回主内存。当多个线程同时读写某个内存数据时,就会涉及到线程并发的问题。涉及到三个特 性:原子性,有序性,可见性。
简单说下这个三个特性的概念:
switch(线程特性){
case (可见性):
一个数据在多个线程中都存在副本的时候,任何一个线程对共享变量的修改,其它线程都应该看到被修改之后的值。
break;
case(有序性):
线程的有序性即按代码的先后顺序执行。很经典的就是银行的存钱取钱问题,比如A线程负责取钱,B线程负责取钱,账户里面有100块,这时候B和A都读取了账户余额,100块,这时B取出了10块,写入主内存后这时候账户还有90块,但A知道的是100块然后存了10块,再写入主内存就是110块,这显然是不对的,没有保存线程的有序性。
break;
case ( 原子性):
原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。
Java中的原子操作包括:
1)除long和double之外的基本类型的赋值操作
2)所有引用reference的赋值操作
3)java.concurrent.Atomic.* 包中所有类的一切操作。
线程之间的交互只能通过共享数据来实现,而不能直接传递数据。
同步是为了解决多个线程对共享数据的访问和操作混乱达不到预期效果这种情况而引入的机制。
break;
}

Synchronized

看如下代码:在main方法中:

for (int index = 0; index < 3; index++) {
    new Thread() {   
     @Override
public void run() {  
for (int i = 0; i < 1000; i++) { 
try {                 
   Thread.sleep(1);       
         } catch (InterruptedException e) {      
              e.printStackTrace();                }         
       incTestNum();   }    
    }    }.start();
}
new Thread() { 
   @Override   
 public void run() { 
       for (int i = 0; i < 300; i++) {    
        try {             
   Thread.sleep(10);        
    } catch (InterruptedException e) {    
            e.printStackTrace();            }    
        getTestNum();        } 
   }}.start();
private static void incTestNum() {  
  i++;    j++;}
private static void getTestNum() {   
 System.out.println("i===========" + i + ";j============" + j);}

我们得到 的结果是:

i===========63;j============63
i===========93;j============93
i===========121;j============122
i===========151;j============152
i===========180;j============182
i===========210;j============212
i===========240;j============242
i===========267;j============270

可以看到j有可能会比i大,这是在多线程并发操作i和j 的时候,并没有同步线程,此时同时操作i和j不具有原子性。并且i++本身也不是原子操作,先读取i,再执行i+1;然后再赋值给i;然后再写入内存。但直观来说应该i比j大,这是应该在读取i之后和读取j之前加操作又执行了多次。导致看到的j比i大。
当我们加上在线程里面加上synchronized之后:

private synchronized static void incTestNum() {   
 i++;    j++;}
private synchronized static void getTestNum() {    
System.out.println("i===========" + i + ";j============" + j);}

结果:

i===========92;j============92
i===========121;j============121
i===========150;j============150
i===========182;j============182
i===========209;j============209
i===========240;j============240
i===========269;j============269
......
i===========3000;j============3000

从结果可以看出,用synchronized锁住的方法是同步执行的,并且得到了正确的结果。
使用synchronized修饰的方法或者代码块可以看成是一个原子操作。如果一个线程获取了锁,其它线程需要获取锁来执行的时候,其它线程就进入了等待锁的释放。这个过程是阻塞的。
一个线程执行互斥代码过程如下:

  1. 获得同步锁;
  2. 清空工作内存;
  3. 从主内存拷贝对象副本到工作内存;
  4. 执行代码(计算或者输出等);
  5. 刷新主内存数据;
  6. 释放同步锁。
    所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

如果在静态方法上加synchronized,那么作用等同于:

void method{
   synchronized(Obl.class)
   }
}

既然要同步我们就要用线程之间的同享对象作为锁,所以下面方式是错误的使用:

Object lock=new Object();
synchronized(lock){

}

使用同步方法的时候:

private synchronized  void test(){    }

等价于:

private void test(){
synchronized(this){
}
}

jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升。

volatile

关于volatile的实现原理可以看看这篇文章:
深入分析Volatile的实现原理
volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的。这就保证了可见性
我们在上面个例子的共享变量加上volatile关键:

static volatile int i = 0, j = 0;

再来看看运行结果:

i===========241;j============241
i===========272;j============271
i===========301;j============301
i===========329;j============330
i===========359;j============360
i===========390;j============390
......
i===========2984;j============2993

这看起来加了volatile和没有加是一样的效果,看起来线程都没有同步。原因是volatile不能保证操作的原子性。也就不能保证i++和j++的原子性,当A,B线程读取i的值假设此时i=10,然后A线程执行+1再写入,刷入主内存中,此时主内存i的值是11,然后现在B线程再执行+1写入主内存,此时主内存中i的值被还是11,但此时正常情况应该是12 的。这就是为什么最后的结果i和j都比3000小。
声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用。如i++;i=i+1;
当且仅当满足以下所有条件时,才应该使用volatile变量:

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
  • 该变量没有包含在具有其他变量的不变式中。
  • 访问变量不需要加锁
    通常使用在如下情况:
static class StopTester implements Runnable { 
   private  boolean stop = false;
public void stopMe() {
stop=true;
}
@Override
public void run() {
while(!stop){
//TODO
}
}
}

volatile与synchronized的区别

  • volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。
  • volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
  • volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
  • volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
  • volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
  • volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 转自:http://www.cnblogs.com/dolphin0520/p/3920373.html vola...
    王帅199207阅读 452评论 0 0
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,174评论 11 349
  • 真心相爱的两个人,不会输给外貌距离,不会输给身高年龄,不会输给前任小三,不会输给别人的流言蜚语,不会输给父母的反对...
    酒芯话阅读 102评论 0 0
  • 谁让你破涕为笑 首发:沐遥诗雨(MoyleSY) 文:李沐遥 . 有些时光是空的 像在鼓起的窗帘后探头探脑 偶尔又...
    李沐遥阅读 443评论 2 4