对synchronized的一点理解

定义

Java中具有通过synchronized实现的内置锁,内置锁获取锁和释放锁的过程是隐式的,进入synchronized修饰的代码就获得锁,离开相应的代码就释放锁。

作用

当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只有一个线程执行该代码。

使用

synchronized主要有两种使用方法:synchronized方法和synchronized代码块。

  • synchronized方法:
public synchronized void foo1() {
    System.out.println("synchronized methoed");
}

注意:synchronized方法只能保证被修饰的方法为互斥访问,而不能保证未被synchronized方法互斥访问。

public class SynchronizedMethod {
    //synchronized方法A
    public synchronized void synchronizedMethodA() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
    }
    //synchronized方法B
    public synchronized void synchronizedMethodB() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
    }
    //非synchronized方法
    public void notSynchronizedMethod() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in notSynchronizedMethod");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out notSynchronizedMethod");
    }
}

public class SynchronizedTest {

    public static void main(String[] args){
        //设置两个对象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        Thread t1 = new Thread(new MyRunnable(method,0), "thread1");
        Thread t2 = new Thread(new MyRunnable(method,1), "thread2");
        Thread t3 = new Thread(new MyRunnable(method,2), "thread3");
        //使用另一个对象作为对比
        Thread t4 = new Thread(new MyRunnable(anotherMethod,0), "thread4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        private int idx;
        public MyRunnable(SynchronizedMethod method, int idx){
            this.method = method;
            this.idx = idx;
        }
        @Override
        public void run() {
            try {
                if (idx %3 == 0)
                    method.synchronizedMethodA();
                else if (idx % 3 == 1)
                    method.notSynchronizedMethod();
                else
                    method.synchronizedMethodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
输出结果:
thread1 enter in synchronizedMethodA
thread2 enter in notSynchronizedMethod
thread4 enter in synchronizedMethodA
thread4 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 get out notSynchronizedMethod
thread3 enter in synchronizedMethodB
thread3 get out synchronizedMethodB

如上,根据结果,对比线程1和线程3可以知道synchronized方法保证了同一对象的互斥访问。对比线程1和线程2可以知道synchronized方法只会保证synchronized修饰的互斥访问。对比线程1和4可以知道,synchronized方法为对象锁,不能保证不同对象的互斥访问。

  • synchronized代码块:
public void foo2() {
    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

synchronized代码块中的this是指当前对象。也可以将this替换成其他对象,例如将list替换成obj,则foo2()在执行synchronized(obj)时获取的就是obj的同步锁。

public class InsertData {
    private ArrayList<Integer> list1 = new ArrayList<>();
    private ArrayList<Integer> list2 = new ArrayList<>();

    public void insertData1(Thread t){
        synchronized (this){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list1数据" + i);
                list1.add(i);
            }
        }
    }

    public void insertData2(Thread t){
        synchronized (this){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list2数据" + i);
                list2.add(i);
            }
        }
    }
}

public class SychronizedTest2 {

    public static void main(String[] args){
        InsertData insertData = new InsertData();
        Thread t1 = new Thread(new MyRunnable(insertData,0),"thread1");
        Thread t2 = new Thread(new MyRunnable(insertData,1),"thread2");
        t1.start();
        t2.start();
    }

    static class MyRunnable implements Runnable{
        private InsertData insertData;
        private int idx;

        public MyRunnable(InsertData insertData, int idx){
            this.insertData = insertData;
            this.idx = idx;
        }

        @Override
        public void run() {
            if (idx % 2 == 0)
                insertData.insertData1(Thread.currentThread());
            else
                insertData.insertData2(Thread.currentThread());
        }
    }

}
输出结果:
thread2在插入list2数据0
thread2在插入list2数据1
thread2在插入list2数据2
thread2在插入list2数据3
thread2在插入list2数据4
thread1在插入list1数据0
thread1在插入list1数据1
thread1在插入list1数据2
thread1在插入list1数据3
thread1在插入list1数据4

根据结果,我们可以看到两个线程时互斥访问InsertData对象的。如果我们只是希望list1和list2分别被互斥访问,而不是互斥访问InsertData对象,那么可以修改如下:

public class InsertData {
    private ArrayList<Integer> list1 = new ArrayList<>();
    private ArrayList<Integer> list2 = new ArrayList<>();

    public void insertData1(Thread t){
        synchronized (list1){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list1数据" + i);
                list1.add(i);
            }
        }
    }

    public void insertData2(Thread t){
        synchronized (list2){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list2数据" + i);
                list2.add(i);
            }
        }
    }
}

输出结果:
thread1在插入list1数据0
thread2在插入list2数据0
thread1在插入list1数据1
thread2在插入list2数据1
thread1在插入list1数据2
thread2在插入list2数据2
thread1在插入list1数据3
thread2在插入list2数据3
thread1在插入list1数据4
thread2在插入list2数据4

根据结果,我们可以看到两个线程是并行的。

建议:尽量使用synchronized代码块,因为synchronized代码块可以更精确地控制冲突限制访问区域,有时候表现地更高效。

public class SynchronizedTest3 {

    public synchronized void synMethod(){
        for (int i = 0; i < 1000000; i ++);
    }

    public void synBlock(){
        synchronized (this){
            for (int i = 0; i < 1000000; i ++);
        }
    }

    public static void main(String[] args){
        SynchronizedTest3 test3 = new SynchronizedTest3();
        long start,end;
        start = System.currentTimeMillis();
        test3.synMethod();
        end = System.currentTimeMillis();
        System.out.println("synMethod() takes " + (end - start) + " ms");
        start = System.currentTimeMillis();
        test3.synBlock();
        end = System.currentTimeMillis();
        System.out.println("synBlock() takes " + (end - start) + " ms");
    }

}

输出结果:
synMethod() takes 3 ms
synBlock() takes 2 ms

实例锁和全局锁

  • 实例锁:锁在某一个实例对象上。如果类是单例,那么该锁也具有全局锁的概念。其对应的是synchronized关键字。
  • 全局锁:该锁针对的是类,无论实例多少个对象,线程都共享该锁。全局锁对应的是static synchronized。

我们先来验证实例锁:对于同一实例必须互斥访问,而不同实例是可以并行。

public class SynchronizedMethod {
    //synchronized方法A
    public synchronized void synchronizedMethodA() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
    }
    //static synchronized方法B
    public static synchronized void synchronizedMethodB() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
    }
}

public class SynchronizedTest {

    public static void main(String[] args){
        //设置两个对象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        //验证synchronized
        Thread t1 = new Thread(new MyRunnable(method), "thread1");
        Thread t2 = new Thread(new MyRunnable(method), "thread2");
        Thread t3 = new Thread(new MyRunnable(anotherMethod), "thread3");
        //验证static synchronized
        //Thread t4 = new Thread(new MyRunnable(method,1), "thread4");
        //Thread t5 = new Thread(new MyRunnable(anotherMethod,1), "thread5");
        t1.start();
        t2.start();
        t3.start();
        //t4.start();
        //t5.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        public MyRunnable(SynchronizedMethod method){
            this.method = method;
        }
        @Override
        public void run() {
            try {
                    method.synchronizedMethodA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果:
thread3 enter in synchronizedMethodA
thread1 enter in synchronizedMethodA
thread3 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 enter in synchronizedMethodA
thread2 get out synchronizedMethodA

我们可以看到线程1和3为不同实例,因此可以并行处理,而线程1和2是同一个实例,必须互斥访问。

接下来,我们再来验证全局锁

public class SynchronizedTest {

    public static void main(String[] args){
        //设置两个对象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        //验证static synchronized
        Thread t4 = new Thread(new MyRunnable(method), "thread4");
        Thread t5 = new Thread(new MyRunnable(anotherMethod), "thread5");
        t4.start();
        t5.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        public MyRunnable(SynchronizedMethod method){
            this.method = method;
        }
        @Override
        public void run() {
            try {
                    method.synchronizedMethodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果:
thread5 enter in synchronizedMethodB
thread5 get out synchronizedMethodB
thread4 enter in synchronizedMethodB
thread4 get out synchronizedMethodB

我们可以看到虽然线程4和5为不同实例,但两者却无法进行并行处理。

基本原则

  • 当一个线程访问“某对象”的synchronized方法“或者”synchronized代码块“时,其他线程对”该对象“的”synchronized方法“或者”synchronized代码块“将被阻塞。
  • 当一个线程访问”某对象“的”synchronized方法“或者”synchronized方法块“时,其他线程可以访问”该对象“的非同步代码块。
  • 当一个线程访问”某对象“的”synchronized方法“或者”synchronized方法块“时,其他线程对”该对象“的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

synchronized原理

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

推荐阅读更多精彩内容