Synchronized三种用法

首先我们了解到Java中的线程同步锁可以是任意对象。
这里我们介绍synchronized的三种应用方式:

1.作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;

2.作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;

3.作用于代码块,这需要指定加锁的对象,对所给的指定对象加锁,进入同步代码前要获得指定对象的锁。

这三种应用方式接下来分别介绍

synchronized修饰实例方法(普通方法)

  使用时,作用范围为整个函数,这里所谓的实例锁就是调用该实例方法(不包括静态方法)的对象。不多BB,上代码:
【demo1】

public class SyncTest implements Runnable{
    //共享资源变量
    int count = 0;

    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            increaseCount();
            System.out.println(Thread.currentThread().getName()+":"+count++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest syncTest1 = new SyncTest();
//        SyncTest syncTest2 = new SyncTest();
        Thread thread1 = new Thread(syncTest1,"thread1");
        Thread thread2 = new Thread(syncTest1, "thread2");
        thread1.start();
        thread2.start();
    }
}
 /**
     * 输出结果
     thread1:0
     thread1:1
     thread1:2
     thread1:3
     thread1:4
     thread2:5
     thread2:6
     thread2:7
     thread2:8
     thread2:9
     */

  代码中开启了两个线程去操作一个变量(共享变量),count++是先读取值,再写回一个新值。我们想一下,如果第一个线程执行这一过程中,第二个线程拿到写回之前的count值,做count++操作,那么这就造成了线程不安全。所以这里在run方法加上synchronized,获取一个对象锁,代码中的实例锁就是syncTest1了。
  同时我们从输出结果看出:当一个线程正在访问一个对象synchronized实例方法时,别的线程是访问不了的。一个对象一把锁说的就是这个,当线程获取了该对象的锁后,其他线程无法获取该对象的锁,当然就访问不了该对象的synchronized方法,但是!但是!但是!可以访问该对象的其他未被synchronized修饰的方法。
  如果是一个线程 A 需要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另一个线程 B 需要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是允许的,因为两个实例对象锁并不同相同,此时如果两个线程操作数据并非共享的,线程安全是有保障的,遗憾的是如果两个线程操作的是共享数据,那么线程安全就有可能无法保证了。我们把上面代码中的main方法中的注释放开,表达这一线程不安全的现象
【demo2】

public class SyncTest implements Runnable{
    //共享资源变量
    int count = 0;

    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+":"+count++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest syncTest1 = new SyncTest();
        SyncTest syncTest2 = new SyncTest();
        Thread thread1 = new Thread(syncTest1,"thread1");
        Thread thread2 = new Thread(syncTest2, "thread2");
        thread1.start();
        thread2.start();
    }
    /**
     * 输出结果
        thread1:0
        thread2:0
        thread1:1
        thread2:1
        thread1:2
        thread2:2
        thread1:3
        thread2:3
        thread1:4
        thread2:4
     */
}

  我们从输出结果来看,两个线程可能同时拿到共享变量去做count++操作。上述操作中虽然我们的run方法还是使用synchronized修饰,但是我们new了两个实例。这就意味存在了两个不同的实例锁,thread1和thread2分别进入了syncTest1和syncTest2的实例锁,当然保证不了线程安全。但是我们也有解决方案啦:如果synchronized修饰的是静态方法呢?下面我们再介绍修饰静态方法。

synchronized修饰静态方法

  我们知道静态方法是不属于当前实例的,而是属性类的,那么这个锁就是类的class对象锁,上述问题引刃而解,请看代码:
【demo3】

public class SyncTest implements Runnable {
    //共享资源变量
    static int count = 0;

    @Override
    public synchronized void run() {
        increaseCount();
    }

    private synchronized static void increaseCount() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + count++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest syncTest1 = new SyncTest();
        SyncTest syncTest2 = new SyncTest();
        Thread thread1 = new Thread(syncTest1, "thread1");
        Thread thread2 = new Thread(syncTest2, "thread2");
        thread1.start();
        thread2.start();
    }
    /**
     * 输出结果
     thread1:0
     thread1:1
     thread1:2
     thread1:3
     thread1:4
     thread2:5
     thread2:6
     thread2:7
     thread2:8
     thread2:9
     */
}

  瞧瞧输出结果,问题解决了没?同样是new了两个不同实例,却保持了线程同步。那是我们synchronizd修饰的是静态方法,run方法中调用这个静态方法,再说一次 静态方法不属于当前实例,而是属于类。所以这个方案其实是用的一个把锁,而这个锁就是这个类的class对象锁。
  需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁(结合demo2,demo3)。

synchronized修饰代码块

  首先这个使用时的场景是:在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。所以他的作用范围为synchronizd(obj){}的这个大括号中
【demo4】

public class SyncTest implements Runnable {
    //共享资源变量
    static int count = 0;
    private byte[] mBytes = new byte[0];

    @Override
    public synchronized void run() {
        increaseCount();
    }

    private void increaseCount() {
        //假设省略了其他操作的代码。
        //……………………
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + count++);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest syncTest1 = new SyncTest();
        SyncTest syncTest2 = new SyncTest();
        Thread thread1 = new Thread(syncTest1, "thread1");
        Thread thread2 = new Thread(syncTest2, "thread2");
        thread1.start();
        thread2.start();
    }
    /**
     * 输出结果
     thread1:0
     thread2:0
     thread1:1
     thread2:2
     thread2:4
     thread1:3
     thread2:5
     thread1:5
     thread2:7
     thread1:6
     */
}

  从输出结果看出,这个demo并没有保证线程安全,因为我们指定锁为this,指的就是调用这个方法的实例对象。这里我们new了两个不同的实例对象syncTest1,syncTest2,所以有两个锁,thread1与thread2分别进入自己传入的对象锁的线程执行increaseCount方法,做成线程不安全。如果把demo4的成员变量注释放开,并将mBytes传入synchronized后面的括号中,也是线程不安全的结果。这里之所以加上mBytes这个对象是为了说明synchronized后面的括号中是可以指定任意对象充当锁的,而零长度的byte数组对象创建起来将比任何对象都经济。当然,如果要使用这个经济实惠的锁并保证线程安全,那就不能new出多个不同实例对象出来啦。如果你非要想new两个不同对象出来,又想保证线程同步的话,那么synchronized后面的括号中可以填入SyncTest.class,表示这个类对象作为锁,自然就能保证线程同步啦。使用方法为:

synchronized(xxxx.class){
  //todo
}

总结

  1. 修饰普通方法 一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象, 如果多个线程不同对象访问该方法,则无法保证同步。

  2. 修饰静态方法 由于静态方法是类方法, 所以这种情况下锁的是包含这个方法的类,也就是类对象;这样如果多个线程不同对象访问该静态方法,也是可以保证同步的。

  3. 修饰代码块 其中普通代码块 如Synchronized(obj) 这里的obj 可以为类中的一个属性、也可以是当前的对象,它的同步效果和修饰普通方法一样;Synchronized方法 (obj.class)静态代码块它的同步效果和修饰静态方法类似。

参考资料:
http://blog.csdn.net/javazejian/article/details/72828483
http://blog.csdn.net/luoweifu/article/details/46613015

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

推荐阅读更多精彩内容

  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,534评论 0 6
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,766评论 0 10
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,629评论 0 11
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 今年过年,我妹妹又随妹夫去了他们家老宅。据我妹夫说他对老宅其实没有太多记忆,因为当年他出生后不久全家就从村子里搬到...
    酒红甜言阅读 295评论 1 1