volatile与synchronized个人理解

在文章之前,首先要理解一个jvm运行时内存分配机制以及一些定义。

(一)定义:

** 原子性**:
对于一个操作或者多个操作,要不全部执行且在执行过程中不可以被中断,要不直接不执行,这种行为成为原子性。

x = 10;         //能保证原子性,10直接写入x
y = x;         //不能保证原子性,x读取出来,写入y
x++;           //不能保证原子性,x读取出来,自增后,写入x
x = x + 1;     //不能保证原子性,同上

** 可见性**:
指的是当存在多个线程访问一个变量的时候,如果其中一个线程改变这个变量值,对于其他线程是可见的,即其他线程可以立即看到这个改变的变量值。

有序性
有序性:即程序执行的顺序按照代码的先后顺序执行

(二)运行时内存分配机制
在其中一个内存区域中有一块叫jvm虚拟机栈,每个线程都独立拥有一个线程栈。这里先说两个定义名词,但某一对象变量值存储在某一内存空间中,我们这里叫做“主内存”,当某一线程访问该对象变量值,用过对象引用找到该变量值的内存空间,每个线程会先复制这个“主内存”的变量到自己的线程栈中,等待该线程栈的值操作完成后再更新到主内存中,贴下别人整理的图。

线程栈工作模式.jpg

所以,这里会衍生出一个问题。如果有多个线程读取这个“主内存”的变量,由于每次都是单独的线程拷贝这个“主内存”的变量到自己的线程栈,处理完成后才会更新到“主内存”中。所以并发的情况下,不能保证当前拷贝的值是同步进行的。

volatile

volatile可以解决在线程并发时候的可见性,即在某一线程栈改变一个主内存变量的时候,其他线程栈可以马上看到这个变化。但是volatile并不能完全保证原子性。下面再举例说明下为什么不能完全保证原子性。

volatile 的可见性
首先贴下代码,看下一则比较常见的例子

/**
 * 测试Volatile的可见性demo
 * @author siven
 *
 */
public class VolatileVisible{

    private static int siven = 1;
    public static int getSiven(){
        return siven;
    }
    
    public static void work(){
        siven = 2;
    }
}

public static void main(String[] args) {
        volatileVisibleAction();
    }
    
    private static void volatileVisibleAction(){
        new Thread(new Runnable() {
            
            public void run() {
                VolatileVisible.work();
            }
        }).start();
        
        new Thread(new Runnable() {
            
            public void run() {
                System.out.println("siven : " + VolatileVisible.getSiven());
            }
        }).start();
    }

从实际想要的效果,第一个线程将变量siven更新为2,第二个线程再读取siven变量并且打印出来。这样的逻辑是没有什么问题。但是有一定的可能性存在并发问题。线程1改变siven变量值的时候,还没来得及更新到主内存,就被线程2
读取,所以这里有可能读到的是1。读者可以多运行几次,几率是比较小,截图如下。

volatile1.png

因此,假如某一线程栈改变了值,其他线程栈可以立即更新的话就可以避免这种问题。
修改成以下代码:

private volatile static int siven = 1;

所以有volatile的存在,保证线程之间的可见性,当然只能处理多线程并发读取问题,为什么这么说呢?看看下面代码:
VolatileVisible中添加work2方法

public static void work2(){
            siven ++;
}

测试方法

    private static void threadAction(){

        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                
                public void run() {
                    VolatileVisible.work2();
                }
            }).start();
        }


        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println("siven : " + VolatileVisible.getSiven());

    }

从实际效果中,一万个线程执行完成后,siven会自加一万次,所以最终想要的结果应该回事10001,但是经过运行观察,很难可以准确得到10001这个结果,如图所示:

volatile2.png

这里读者会质疑,siven已经用volatile修饰符修饰了,siven变量改变应该对于所有线程栈是可见才对啊。首先这里我们应该了解siven++;自加操作,其实这个操作并不是原子操作,这里面包括内存中取出siven,然后自加1,接着再写入siven,所以在三个操作中有可能是会被中断的。虽然用了volatile修饰保证线程栈中的可见性,但是只限制于读取的时候可见性,也只有线程栈写入完成之后才会立即更新到主内存并且其他线程栈会马上知道

举个例子:A线程、B线程都共同访问siven变量,这时候用得是volatile修饰,因此siven变量对于A、B线程都是可见的。当A对siven变量进行非原子操作(例如自加到2)是有可能出现还没执行到最后,B线程已经读取了旧值1,并且自己也自加到2(原来实际效果是想拿到A线程自加后的结果,在自加成3)。因此volatile只是保证线程之间的可见性,但是不能保证线程中操作的原子性。所以也引出了synchronized

synchronized

首先在理解synchronized中,要了解对象锁和类锁两个定义。每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。其实对象锁与类锁的的存在价值与内置锁一致。只是对象锁与类锁的应用场景不一样,即类锁应用在静态方法,对象锁应用在实例方法。我们都知道,每个类都存在多个实例化对象,但是每个类只有一个class对象,所以实例化对象中的对象锁并不会互相干预。

首先贴下代码:

/**
 * Synchronized 测试方法
 * @author siven
 */

public class SynchronizedWorker {
    
    
    public static void work(String tag){
        
        for (int i = 0; i < 5; i++) {
            System.out.println(tag + " work : " + i);
        }
        
    }

}


private static void synchronizedTest(){
        Thread threadA = new Thread(new Runnable() {

            public void run() {
                SynchronizedWorker.work("A");
            }
        });

        Thread threadB = new Thread(new Runnable() {

            public void run() {
                SynchronizedWorker.work("B");
            }
        });
        
        threadA.start();
        threadB.start();

    }

输出结果:

synchronized1.png

首先这里只是输出了log语句,如果当前不是输出而是改变某一对象里面的变量的时候,很容易出现因为线程并发问题导致对象成员变量被改变或者被重新实例化,特别是android中的回调,很多时候是发生在多线程的,所以很容易发生这种并发问题。如果我们要实际的同步效果,我们可以直接使用synchronized对方法或者代码块进行加锁。例如下面的优化改造:

(一)直接修饰方法


public synchronized static void work(String tag){
        
        for (int i = 0; i < 5; i++) {
            System.out.println(tag + " work : " + i);
        }
        
    }

(二)修饰代码块

    public static void work(String tag){
        
        synchronized (SynchronizedWorker.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(tag + " work : " + i);
            }
        }
        
    }

在synchronized 修饰的方法或者代码块中,会将这个区域进行加锁,当第一个线程进行访问的时候,该线程会获取到该锁,如果第二个线程对这个区域进行访问的时候,如果有其他线程占有的锁还没释放的时候,将会暂时性阻塞,等待其他线程锁释放后自己获取后才可以进行访问。

当然对于前面volatile可以解决可见性,但是不能完全保证原子性的代码案例中也可以通过synchronized 解决,我们只需要该成以下代码即可:

    public static void work2(){
        synchronized (VolatileVisible.class) {
            siven ++;
        }
    }

输出结果:


synchronized2.png

注意问题:

前面也说每个类有多个实例化对象,说明有多个对象锁。如果是synchronized (this)进行代码块加锁,此时只能对当前对象方法进行加锁,与其他对象锁没有任何干预。当然如果是类锁,针对静态方法的,我们没办法通过synchronized (this)进行代码块加锁,可以使用synchronized (**.class)进行加锁

by siven(qq:708854877 email:sy.wu@foxmail.com)

2017.5.26

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

推荐阅读更多精彩内容