燃烧吧!我的并发之魂–synchronized

经历了两个月的沉淀,感觉整体能力有所提升,最近除了 年终总结 也没有什么打算了

高并发这块一致是我的心病,在这年尾,抽刀,奋力一击吧

虽然会用线程,但是总感觉有很多地方让我挺烦心,比如并发和那两个关键字

曾经三次想要突破多线程,但都失败了,只好暂时离开,现在的我感觉应该可以了

本文按照慕课网免费课程敲的,同时也加入了我大量的思考和绘图,希望对你有所帮助

一、多线程的简单回顾

1.入门级

下面WhatIsWrong实现Runnable,并提供一个 静态实例对象计时器i
run方法让i自加10W次,下面的结果是多少?

public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        System.out.println(i);
    }
    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}
复制代码

答案是0,简单讲一下:main中代码顺序执行虽然线程1,2都开启了,

但是程序还是顺序执行的,会立刻走 System.out.println(i); ,实际看来run方法要慢一些

燃烧吧!我的并发之魂--synchronized

2.如何让打印在两个线程完成后才调用

两个方法:1) 让主线程先睡一会 、2) 使用线程对象的join方法
总之就是推迟 System.out.println(i); 的执行时间

2.1:让主线程先睡一会

这个方法很容易想到,但睡多久不好把握,一般小测试1s应该够了

燃烧吧!我的并发之魂--synchronized
public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}
复制代码

2.2.join方法

正规的还是用join吧,他会让该线程先行,使以 System.out.println(i); 被推后

燃烧吧!我的并发之魂--synchronized
public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}

复制代码

3.结果呢?

3.1下面是十次结果

137355  、 114412 、115381 、128482 、151021 、
109093 、  128610 、128144 、122390 、123746
复制代码

3.2从中能看出什么?

1).每次执行结果都不一样

2).它们都大于100000且小于200000

3.3为什么

理论上两个线程,每个线程加100000次,一共应该200000才对

想象一下这个场景:

有两个神枪手对着靶子打(必中),每把枪有100000颗软子弹
靶子有感应器和计数器,当软子弹接触靶子那一刻,计数器加1  
当两个子弹同时接触时,感应器有无法及时反应,只会被记录一次,即计数器只+1
然后两人疯狂扫射,最后看靶子上计数器最终的数值  

可想而知最后计数器上的数应该是小于200000的,所以代码中也类似
两个线程便是神枪手,run的时候开始同时疯狂扫射,i便是靶子
复制代码

3.4:i++发生了什么?

1)
内存中读取i的值 ,2)
i=i+1 ,3)
将结果写回内存

i=9时,若线程2已经在第三步了,但还没写入内存。这时线程1进入,读出i的值仍是9,

从而导致此次结束两个结果都是10,这就是为什么达不到200000的原因

这就相当于两个神枪手同时开枪,靶子未及时反应而导致两颗同弹

燃烧吧!我的并发之魂--synchronized

燃烧吧!我的并发之魂--synchronized

4.怎么解决呢?

先看问题出在哪,是两个人同时开枪对一个靶子

一个人是不能在同一时刻发出两法子弹的,so,方法1:

准备两个靶子,各自统计(像每个足球运动员一个足球一样,10000个人怎么办,然并卵)

方法2:不允许两个人同时开枪,这便是
synchronized

神枪手1在扫射时,神射手2的枪自动死,如果100条线程也是类似,某一刻只能一人开枪

public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();

        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);//200000
    }

    @Override
    public synchronized void run() {//只需轻轻加一个synchronized即可
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}
复制代码

二、同步

燃烧吧!我的并发之魂--synchronized

0.测试代码(此时还未同步)

先看一下干了什么事:线程创建不说了,run方法中:

打印信息 –> 当前线程睡三秒 –> 打印运行结束 (如下图)

根据时间线可以看出来打印结果(可以看出两个人一起睡了,这还得了…)

燃烧吧!我的并发之魂--synchronized
public class SynObj_Block implements Runnable {
    static SynObj_Block instance = new SynObj_Block();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        System.out.println("对象锁,代码块形式--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("运行结束,name:" + Thread.currentThread().getName());
    }
}
复制代码

打印结果:

对象锁,代码块形式--name:Thread-1
对象锁,代码块形式--name:Thread-0
运行结束,name:Thread-0
运行结束,name:Thread-1
All Finished
复制代码

1.对象锁之同步代码块锁

上面说两个线程一起睡了,线程1先睡,线程2进来也睡了,能忍吗?不能忍!

快把哥的四十米大刀,不对,是大锁拿来,在我睡觉前先把门锁上

线程1进来睡,然后把门锁上,线程2就进不来,只能等线程1把锁打开

燃烧吧!我的并发之魂--synchronized

1.1:同步代码块的添加

其他代码不变,就不贴了

@Override
public void run() {
    synchronized (this) {
        System.out.println("对象锁,代码块形式--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("运行结束,name:" + Thread.currentThread().getName());
    }
}
复制代码

1.2:运行结果

可见线程1睡完,线程2才能进来睡

对象锁,代码块形式--name:Thread-0
运行结束,name:Thread-0
对象锁,代码块形式--name:Thread-1
运行结束,name:Thread-1
All Finished
复制代码

1.3:锁对象

等等,这this是什么鬼?–有点基础的都知道是当前类对象

System.out.println(this);// top.toly.并发.SynObj_Block@77c89a74

同步代码块synchronized()接收一个对象,该对象可任意指定:
Object lock = new Object();
synchronized (lock) {//TODO}  
新建一个空对象也可以
复制代码

1.4:多把锁

也会你会说:既然随便一个对象都可以当做锁对象,Java自己给内置个呗

还传个参数,累不累人。等等,存在即合理,且看下面…

想一下如果一个房间两张床,你上来把门锁了,岂不是不合理?

那该怎么办?两扇门,两把不同的锁呗(就像两个人合租一间大房子一样)

你可以根据图中 时间线 好好想想(画个图也不是那么容易的…且看且珍惜)

燃烧吧!我的并发之魂--synchronized
/**
 * 作者:张风捷特烈
 * 时间:2018/12/28 0028:19:16
 * 邮箱:1981462002@qq.com
 * 说明:对象锁--代码块锁
 */
public class SynObj_Block implements Runnable {
    static SynObj_Block instance = new SynObj_Block();
    Object lock1 = new Object();//第一把锁
    Object lock2 = new Object();//第二把锁
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();


        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        synchronized (lock1) {
            System.out.println("lock1开始--name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock1结束,name:" + Thread.currentThread().getName());
        }
        synchronized (lock2) {
            System.out.println("lock2开始,代码块形式--name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock2结束,name:" + Thread.currentThread().getName());
        }
    }
}
复制代码
对象锁lock1,代码块形式--name:Thread-0
lock1睡醒了,name:Thread-0
对象锁lock1,代码块形式--name:Thread-1
对象锁lock2,代码块形式--name:Thread-0
lock1睡醒了,name:Thread-1
lock2睡醒了,name:Thread-0
对象锁lock2,代码块形式--name:Thread-1
lock2睡醒了,name:Thread-1
All Finished
复制代码

有什么好处?两人合租房有什么好处,多把锁就有什么好处。

可看出即完成任务,又减少了2秒,这也就两个线程而已

如果百万级的线程数,哪怕微小的效率提升都是有价值的

2.对象锁之普通方法锁

正如1.4所想:我就是想简单的加个锁,每次同步代码块还有传个对象,挺烦的

所以有一个叫方法锁,什么对象每个类都有?答案: this ,方法锁的对象默认是this

2.1:使用

/**
 * 作者:张风捷特烈
 * 时间:2018/12/28 0028:19:16
 * 邮箱:1981462002@qq.com
 * 说明:对象锁--普通方法锁
 */
public class SynObj_Method implements Runnable {
    static SynObj_Method instance = new SynObj_Method();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        sleep3ms();
    }

    public synchronized void sleep3ms() {
        System.out.println("方法锁测试--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束,name:" + Thread.currentThread().getName());
    }
}
复制代码

2.2:打印结果

和同步代码块一致

方法锁测试--name:Thread-0
结束,name:Thread-0
方法锁测试--name:Thread-1
结束,name:Thread-1
All Finished
复制代码

2.3:如何证明方法锁的锁对象是this

@Override
public void run() {
    sleep3ms();
    synchronized (this){
        System.out.println("测试开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("测试结束,name:" + Thread.currentThread().getName());
    }
}

public synchronized void sleep3ms() {
    System.out.println("方法锁测试--name:" + Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("结束,name:" + Thread.currentThread().getName());
}
复制代码
方法锁开始--name:Thread-0
方法锁结束,name:Thread-0
同步代码块测试开始--name:Thread-0
同步代码块测试结束,name:Thread-0
方法锁开始--name:Thread-1
方法锁结束,name:Thread-1
同步代码块测试开始--name:Thread-1
同步代码块测试结束,name:Thread-1
All Finished
复制代码

加上this同步代码块后:可见开始与结束两两配对

说明方法锁和同步代码块的this锁是一把锁,也就是只有一扇门,不须一个一个睡

2.4:反证

@Override
public void run() {
    sleep3ms();
    synchronized (""){
        System.out.println("测试开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("测试结束,name:" + Thread.currentThread().getName());
    }
}

public synchronized void sleep3ms() {
    System.out.println("方法锁测试--name:" + Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("结束,name:" + Thread.currentThread().getName());
}
复制代码
方法锁开始--name:Thread-0
方法锁结束,name:Thread-0
方法锁开始--name:Thread-1
同步代码块测试开始--name:Thread-0
方法锁结束,name:Thread-1
同步代码块测试结束,name:Thread-0
同步代码块测试开始--name:Thread-1
同步代码块测试结束,name:Thread-1
All Finished
复制代码

如果锁不是this,这里简单点用"",可见Thread-0的结束后

Thread-1的 方法锁开始 和Thread-0的 同步代码块测试开始 是同时打印出来的

说明有两扇门,那两把锁不是同一把,也反向表明,非this会产生两把锁

综上正反两面,我们可以感受到方法锁的锁对象是this

3.类锁之静态方法锁(static方法+synchronized)

说是类锁,实质上是使用了Class对象当做锁,非要较真的话,你可以把他看作对象锁

Class对象有什么特点:一个类可以有多个对象,但 仅有一个 Class对象

这就可以导致:类锁只能在同一时刻被一个对象拥有

3.1.static方法+synchronized

普通方法+synchronized 但是两个 不同的Runnable对象 线程

public class Syn_Static_Method implements Runnable {
    static Syn_Static_Method instance1 = new Syn_Static_Method();
    static Syn_Static_Method instance2 = new Syn_Static_Method();
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance1);
        Thread thread_2 = new Thread(instance2);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }
    @Override
    public void run() {
        sleep3ms();
    }
    public synchronized void sleep3ms() {
        System.out.println("静态方法锁开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("静态方法锁开始,name:" + Thread.currentThread().getName());
    }
}
复制代码

好吧,用脚趾头想想也知道互不影响

这相当于两个人有两个家,各自进各自的家睡觉天经地义

你加synchronized锁你家的门管我什么事,所以synchronized这时并没用处

静态方法锁开始--name:Thread-1
静态方法锁开始--name:Thread-0
静态方法锁开始,name:Thread-0
静态方法锁开始,name:Thread-1
All Finished
复制代码

我们都知道static关键字修饰的方法、变量,是可以令于类名(Class对象)的

也就是不需要对象便可以运行,由static修饰的方法是不能用this对象的

这就是为什么加一个static,锁就不同了的原因,至于锁是什么,除了它的老大还有人选吗?

/**
 * 作者:张风捷特烈
 * 时间:2018/12/28 0028:19:16
 * 邮箱:1981462002@qq.com
 * 说明:对象锁--静态方法锁
 */
public class Syn_Static_Method implements Runnable {
    //同上...略
    public static synchronized void sleep3ms() {//我就轻轻加个static
       //同上...略
    }
}
复制代码
静态方法锁开始--name:Thread-0
静态方法锁开始,name:Thread-0
静态方法锁开始--name:Thread-1
静态方法锁开始,name:Thread-1
All Finished
复制代码

符合预期:这样就将一个类给锁起来了,只要是这个类的对象

都会生效,这也是它的优势,也是static的本意:静态,具有全局控制力

4.类锁之Class对象锁

相当于把 static+synchronized 拆出来

/**
 * 作者:张风捷特烈
 * 时间:2018/12/28 0028:19:16
 * 邮箱:1981462002@qq.com
 * 说明:对象锁--class锁
 */
public class Syn_Class implements Runnable {
    static Syn_Class instance1 = new Syn_Class();
    static Syn_Class instance2 = new Syn_Class();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance1);
        Thread thread_2 = new Thread(instance2);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        sleep3ms();
    }

    public void sleep3ms() {
        synchronized (Syn_Class.class) {
            System.out.println("class锁开始--name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("class锁开始,name:" + Thread.currentThread().getName());
        }
    }
}
复制代码
class锁开始--name:Thread-0
class锁开始,name:Thread-0
class锁开始--name:Thread-1
class锁开始,name:Thread-1
All Finished
复制代码

5.现在回头来看

synchronized: 同步的

官宣:
同步方法支持一种简单的策略来[防止线程干扰]和[内存一致性错误]:
如果一个对象变量对多个线程可见,则对它的所有读写都是通过同步方法完成的

民宣:
保证同一时刻最多只一个线程执行该段代码,来保证并发安全
复制代码

三、多线程访问方法的一些情况

感觉有点…麻烦

1.两个线程访问一个对象的普通同步方法
2.两个线程访问两个对象的普通同步方法
3.两个线程访问静态同步方法
4.两个线程分别访问普通同步方法和非同步方法
5.两个线程分别访问一个对象的不同普通同步方法
6.两个线程分别访问静态同步和非静态同步方法
方法抛出异常后,会释放锁
复制代码

1.两个线程访问一个对象的普通同步方法

二-->2 中的例子: 线程1,2 访问一个对象 instance 的同步方法: sleep3ms
同一个对象,需要等待锁的释放,才能进入普通同步方法

2.两个线程访问两个对象的普通同步方法

二-->3-->3.1 中第一个小例子(用脚趾头想的那个)

同一类的两个不同对象的普通同步方法,对于两个线程而言,同步是无用的

3.两个线程访问静态同步方法

二-->3-->3.1 第二个小例子,轻轻加了个static

由于静态同步方法的锁是class,锁对该类的所有对象都有效

4.两个线程分别访问普通同步方法和非同步方法

线程2的方法没加锁(非同步方法),就进来睡了呗,也没什么特别的

注意两头几乎同时执行,测试了几次,两头的先后顺序不定

燃烧吧!我的并发之魂--synchronized
/**
 * 作者:张风捷特烈
 * 时间:2018/12/29 0029:11:31
 * 邮箱:1981462002@qq.com
 * 说明:两个线程分别访问普通同步方法和非同步方法
 */
public class SynOrNot implements Runnable {
    static SynOrNot instance = new SynOrNot();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            sleep3msSync();
        } else {
            sleep3msCommon();
        }
    }

    public void sleep3msCommon() {
        System.out.println("非同步方法开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("非同步方法结束,name:" + Thread.currentThread().getName());
    }

    public synchronized void sleep3msSync() {
        System.out.println("同步方法开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("同步方法结束,name:" + Thread.currentThread().getName());
    }
}
复制代码
同步方法开始--name:Thread-0
非同步方法开始--name:Thread-1
同步方法结束,name:Thread-0
非同步方法结束,name:Thread-1
All Finished
复制代码

5.两个线程分别访问一个对象的不同普通同步方法

由于普通同步方法是this锁,所以对不同普通同步方法锁是一致的,都生效

燃烧吧!我的并发之魂--synchronized
/**
 * 作者:张风捷特烈
 * 时间:2018/12/29 0029:11:31
 * 邮箱:1981462002@qq.com
 * 说明:两个线程分别访问一个对象的不同普通同步方法
 */
public class SynOfTwo implements Runnable {
    static SynOfTwo instance = new SynOfTwo();
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            sleep3msSync1();
        } else {
            sleep3msSync2();
        }
    }

    public synchronized void sleep3msSync2() {
        System.out.println("sleep3msSync2方法开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sleep3msSync2结束,name:" + Thread.currentThread().getName());
    }

    public synchronized void sleep3msSync1() {
        System.out.println("sleep3msSync1开始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sleep3msSync1结束,name:" + Thread.currentThread().getName());
    }
}
复制代码
sleep3msSync1开始--name:Thread-0
sleep3msSync1结束,name:Thread-0
sleep3msSync2方法开始--name:Thread-1
sleep3msSync2结束,name:Thread-1
All Finished
复制代码

6.两个线程分别访问静态同步和普通同步方法

不测试都知道:一个是class锁,一个是this锁,锁不同,不生效

在第5个的基础上加上static关键字,其余不变,结果不出所料

燃烧吧!我的并发之魂--synchronized
public static synchronized void sleep3msSync2() {
复制代码
sleep3msSync1开始--name:Thread-0
sleep3msSync2方法开始--name:Thread-1
sleep3msSync2结束,name:Thread-1
sleep3msSync1结束,name:Thread-0
All Finished
复制代码

7.抛出异常后,释放锁

可以看出线程1抛异常后,线程2是可以正常运行的(说明线程1的锁已经被释放)

就像线程1在睡觉,睡着睡着仙逝了,房东(JVM)会把它抬走,把锁给下一个人,继续睡…

在第5个的代码上稍微修改: int a=1/0;//异常

燃烧吧!我的并发之魂--synchronized
public synchronized void sleep3msSync1() {
    System.out.println("sleep3msSync1开始--name:" + Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
        int a=1/0;//异常
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("sleep3msSync1结束,name:" + Thread.currentThread().getName());
}
复制代码
sleep3msSync1开始--name:Thread-0
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
sleep3msSync2方法开始--name:Thread-1
	at top.toly.并发.SynOfError.sleep3msSync1(SynOfError.java:54)
	at top.toly.并发.SynOfError.run(SynOfError.java:33)
	at java.base/java.lang.Thread.run(Thread.java:844)
sleep3msSync2结束,name:Thread-1
All Finished
复制代码

一把锁只能由一个线程获取,没拿到锁的线程必须等待

不同的锁之间互不影响(相当于进不同的门,互不干扰,无需等待)

无论正常执行还是抛出异常,都会释放锁

8、synchronized的性质

可重入:同一线程外层函数获取锁之后,内层函数可以直接再次获取该锁
好处:避免死锁,提高封装性
粒度:线程范围  
即synchronized修饰的同步方法内部`并非只能`调用同步方法
复制代码
不可中断:比如我线程1要小睡个十万年,那线程2就要在门等上十万年(想走都不行)。
复制代码

四、Java内存模型(JMM–Java Memory Model)

1.Java内存模型的概念

描述Java程序中各变量(线程共享变量)的访问规则,

即在JVM中将变量存储到内存和从内存中读取变量的底层细节

1.所有的变量都存储在主内存中,
2.每条线程都有自己独立的工作内存。其保存该线程用到的变量副本(主内存变量拷贝)。

规定:
[1]线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
[2]线程无法直接访问非己方工作内存中的变量,线程间变量值的传递需要间接通过主内存。
复制代码
燃烧吧!我的并发之魂--synchronized

2.如何:线程1的修改被线程2看到

1.工作内存1操作共享变量a后 刷新到主内存
2.然后线程2从主内存中 读取 共享变量a值并 拷贝 到自己的工作内存

燃烧吧!我的并发之魂--synchronized

3、synchronized实现可见性

锁定的线程1所做的任何修改都要在释放锁之前从工作内存刷新到主内存

线程2拿到锁时从主内存中拷贝需要的变量到自己的工作内存(从而实现共享变量的可见)

4、缺陷:

效率低:
锁的释放情况少(只能自动释放,或异常)
不能中断等待锁的线程

不灵活:
加锁和释放锁的时机单一,每个锁只有单一的条件
无法知道释放成功获取锁
复制代码

5.注意点

锁对象不能为空,作用域不宜过大,避免死锁
|---锁对象的信息是放在对象头中,所以不能为空
|---作用域过大,导致串行执行的代码变多,效率下降

Lock还是synchronized
|---尽量使用并发包里的原子类
|---synchronized能完成的尽量不去Lock
|---确实需要中断等待、灵活开解锁或Condition可以使用Lock锁

多线程访问同步方法的几种情况
复制代码

死锁简单演示

/**
 * 作者:张风捷特烈
 * 时间:2018/12/29 0029:11:31
 * 邮箱:1981462002@qq.com
 * 说明:死锁简单演示
 */
public class SynKill implements Runnable {
    static SynKill instance1 = new SynKill();
    static SynKill instance2 = new SynKill();
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance1);
        Thread thread_2 = new Thread(instance1);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }
    @Override
    public void run() {
        sleep3msSync1();
        sleep3msSync2();
    }

    public void sleep3msSync2() {
        synchronized (instance1) {
            System.out.println("sleep3msSync2方法开始--name:" + Thread.currentThread().getName());
            synchronized (instance2) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("sleep3msSync2结束,name:" + Thread.currentThread().getName());
       }
    }

    public static synchronized void sleep3msSync1() {
        synchronized (instance2) {
            System.out.println("sleep3msSync1方法开始--name:" + Thread.currentThread().getName());
            synchronized (instance1) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("sleep3msSync1结束,name:" + Thread.currentThread().getName());
        }
    }
}
复制代码
燃烧吧!我的并发之魂--synchronized

六、synchronized原理简述

1.定义一个类,其中用一个同步方法

public class Decode {

    private Object obj = new Object();

    public void say(Thread thread) {
        synchronized (obj){

        }
    }

}
复制代码

2.反编译(含同步方法的类):

I:/Java/Base/Thinking/src/top/toly/并发>javac -encoding utf-8 Decode.java

I:/Java/Base/Thinking/src/top/toly/并发>javap -verbose Decode.class
Classfile /I:/Java/Base/Thinking/src/top/toly/并发/Decode.class
  Last modified 2018年12月29日; size 465 bytes
  MD5 checksum 732654b709aafd523b08c943dcb1f235
  Compiled from "Decode.java"
public class top.toly.并发.Decode
  minor version: 0
  major version: 54
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4                          // top/toly/并发/Decode
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // java/lang/Object
   #3 = Fieldref           #4.#20         // top/toly/并发/Decode.obj:Ljava/lang/Object;
   #4 = Class              #21            // top/toly/并发/Decode
   #5 = Utf8               obj
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               say
  #12 = Utf8               (Ljava/lang/Thread;)V
  #13 = Utf8               StackMapTable
  #14 = Class              #22            // java/lang/Thread
  #15 = Class              #23            // java/lang/Throwable
  #16 = Utf8               SourceFile
  #17 = Utf8               Decode.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = Utf8               java/lang/Object
  #20 = NameAndType        #5:#6          // obj:Ljava/lang/Object;
  #21 = Utf8               top/toly/并发/Decode
  #22 = Utf8               java/lang/Thread
  #23 = Utf8               java/lang/Throwable
{
  public top.toly.并发.Decode();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field obj:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 9: 0
        line 11: 4

  public void say(java.lang.Thread);
    descriptor: (Ljava/lang/Thread;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_0
         1: getfield      #3                  // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_2
         6: monitorenter  <---------------monitorenter
         7: aload_2
         8: monitorexit   <---------------monitorexit
         9: goto          17
        12: astore_3
        13: aload_2
        14: monitorexit  <---------------monitorexit
        15: aload_3
        16: athrow
        17: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:
        line 14: 0
        line 16: 7
        line 17: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class top/toly/并发/Decode, class java/lang/Thread, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "Decode.java"

复制代码

3.如果将同步代码块去掉,再反编译

I:/Java/Base/Thinking/src/top/toly/并发>javap -verbose Decode.class
Classfile /I:/Java/Base/Thinking/src/top/toly/并发/Decode.class
  Last modified 2018年12月29日; size 331 bytes
  MD5 checksum 7963d00f1f781bc47a9700c548692617
  Compiled from "Decode.java"
public class top.toly.并发.Decode
  minor version: 0
  major version: 54
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4                          // top/toly/并发/Decode
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#15         // java/lang/Object."<init>":()V
   #2 = Class              #16            // java/lang/Object
   #3 = Fieldref           #4.#17         // top/toly/并发/Decode.obj:Ljava/lang/Object;
   #4 = Class              #18            // top/toly/并发/Decode
   #5 = Utf8               obj
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               say
  #12 = Utf8               (Ljava/lang/Thread;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Decode.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Utf8               java/lang/Object
  #17 = NameAndType        #5:#6          // obj:Ljava/lang/Object;
  #18 = Utf8               top/toly/并发/Decode
{
  public top.toly.并发.Decode();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field obj:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 9: 0
        line 11: 4

  public void say(java.lang.Thread);
    descriptor: (Ljava/lang/Thread;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 14: 0
}
SourceFile: "Decode.java"

复制代码

两次的对比可以看出:obj对象上的东西有点不一样

加了
synchronized 代码块的,obj对象头会有
monitorenter
monitorexit

注意 是加锁时使用的对象obj的对象头

4. monitorentermonitorexit

monitorenter次数为0时:若线程1进入,monitorenter次数+1,线程1成为该Monitor的所有者
若此时线程2进入,由于Monitor的所有者非线程2,线程2只能等待,直到monitorenter次数为0

若线程1进入同步方法后,又调用了一次其他方法,则monitorenter次数+1,方法退出时-1(可重入)
当monitorenter次数为0,说明:线程1的该同步方法执行完毕,将工作内存刷新到主内存,并释放锁  
这时monitorenter次数为0,线程2允许进入,monitorenter次数+1,线程2成为该Monitor的所有者
复制代码

更深的东西以后慢慢来吧,先了解个线程同步的大概,并发的内功也不是一朝一夕能成的

后记:捷文规范

1.本文成长记录及勘误表

项目源码 日期 备注
V0.1 2018-12-29 燃烧吧!我的并发之魂–synchronized

2.更多关于我

笔名 QQ 微信 爱好
张风捷特烈 1981462002 zdl1994328 语言
我的github 我的简书 我的掘金 个人网站

3.声明

1—-本文由张风捷特烈原创,转载请注明

2—-欢迎广大编程爱好者共同交流

3—-个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正

4—-看到这里,我在此感谢你的喜欢与支持

燃烧吧!我的并发之魂--synchronized

原文 

https://juejin.im/post/5c27207ee51d4558873c693d

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 燃烧吧!我的并发之魂–synchronized

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址