前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >燃烧吧!我的并发之魂--synchronized

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

作者头像
张风捷特烈
发布2019-01-03 15:15:56
5180
发布2019-01-03 15:15:56
举报
零、前言

经历了两个月的沉淀,感觉整体能力有所提升,最近除了年终总结也没有什么打算了 高并发这块一致是我的心病,在这年尾,抽刀,奋力一击吧 虽然会用线程,但是总感觉有很多地方让我挺烦心,比如并发和那两个关键字 曾经三次想要突破掉多线程,但都失败了,只好暂时离开,现在的我感觉应该可以了 本文按照慕课网免费课程敲的,同时也加入了我大量的思考和绘图,希望对你有所帮助


一、多线程的简单回顾
1.入门级

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

代码语言:javascript
复制
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方法要慢一些

简单分析.png


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

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

2.1:让主线程先睡一会

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

线程休眠.png

代码语言:javascript
复制
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);被推后

线程join.png

代码语言:javascript
复制
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下面是十次结果
代码语言:javascript
复制
137355  、 114412 、115381 、128482 、151021 、
109093 、  128610 、128144 、122390 、123746

3.2从中能看出什么?

1).每次执行结果都不一样 2).它们都大于100000且小于200000


3.3为什么

理论上两个线程,每个线程加100000次,一共应该200000才对 想象一下这个场景:

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

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

3.4:i++发生了什么?

1)内存中读取i的值,2)i=i+1,3)将结果写回内存 i=9时,若线程2已经在第三步了,但还没写入内存。这时线程1进入,读出i的值仍是9, 从而导致此次结束两个结果都是10,这就是为什么达不到200000的原因 这就相当于两个神枪手同时开枪,靶子未及时反应而导致两颗同弹

i++发生了什么.png

不同步会出现什么状况.png

4.怎么解决呢?

先看问题出在哪,是两个人同时开枪对一个靶子 一个人是不能在同一时刻发出两法子弹的,so,方法1: 准备两个靶子,各自统计(像每个足球运动员一个足球一样,10000个人怎么办,然并卵) 方法2:不允许两个人同时开枪,这便是synchronized 神枪手1在扫射时,神射手2的枪自动锁死,如果100条线程也是类似,某一刻只能一人开枪

代码语言:javascript
复制
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++;
        }
    }
}

二、同步锁

几种锁.png

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

先看一下干了什么事:线程创建不说了,run方法中: 打印信息-->当前线程睡三秒-->打印运行结束(如下图) 根据时间线可以看出来打印结果(可以看出两个人一起睡了,这还得了...)

非同步分析.png

代码语言:javascript
复制
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());
    }
}

打印结果:

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

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

上面说两个线程一起睡了,线程1先睡,线程2进来也睡了,能忍吗?不能忍! 快把哥的四十米大刀,不对,是大锁拿来,在我睡觉前先把门锁上 线程1进来睡,然后把门锁上,线程2就进不来,只能等线程1把锁打开

同步代码块.png


1.1:同步代码块的添加

其他代码不变,就不贴了

代码语言:javascript
复制
@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才能进来睡

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

1.3:锁对象

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

代码语言:javascript
复制
System.out.println(this);// top.toly.并发.SynObj_Block@77c89a74

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

1.4:多把锁

也会你会说:既然随便一个对象都可以当做锁对象,Java自己给内置个呗 还传个参数,累不累人。等等,存在即合理,且看下面... 想一下如果一个房间两张床,你上来把门锁了,岂不是不合理? 那该怎么办?两扇门,两把不同的锁呗(就像两个人合租一间大房子一样) 你可以根据图中时间线好好想想(画个图也不是那么容易的...且看且珍惜)

两把锁.png

代码语言:javascript
复制
/**
 * 作者:张风捷特烈
 * 时间: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());
        }
    }
}
代码语言:javascript
复制
对象锁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:使用
代码语言:javascript
复制
/**
 * 作者:张风捷特烈
 * 时间: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:打印结果

和同步代码块一致

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

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

你说this就this?何以见得?

代码语言:javascript
复制
@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());
}
代码语言:javascript
复制
方法锁开始--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:反证
代码语言:javascript
复制
@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());
}
代码语言:javascript
复制
方法锁开始--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对象线程

代码语言:javascript
复制
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这时并没用处

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

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

也就是不需要对象便可以运行,由static修饰的方法是不能用this对象的 这就是为什么加一个static,锁就不同了的原因,至于锁是什么,除了它的老大还有人选吗?

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

符合预期:这样就将一个类给锁起来了,只要是这个类的对象 都会生效,这也是它的优势,也是static的本意:静态,具有全局控制力


4.类锁之Class对象锁

相当于把static+synchronized拆出来

代码语言:javascript
复制
/**
 * 作者:张风捷特烈
 * 时间: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());
        }
    }
}

代码语言:javascript
复制
class锁开始--name:Thread-0
class锁开始,name:Thread-0
class锁开始--name:Thread-1
class锁开始,name:Thread-1
All Finished

5.现在回头来看

synchronized:同步的

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

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

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

感觉有点...麻烦

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

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

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


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

二-->3-->3.1中第一个小例子(用脚趾头想的那个) 同一类的两个不同对象的普通同步方法,对于两个线程而言,同步是无用的


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

二-->3-->3.1第二个小例子,轻轻加了个static 由于静态同步方法的锁是class,锁对该类的所有对象都有效


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

线程2的方法没加锁(非同步方法),就进来睡了呗,也没什么特别的 注意两头几乎同时执行,测试了几次,两头的先后顺序不定

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

代码语言:javascript
复制
/**
 * 作者:张风捷特烈
 * 时间: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());
    }
}
代码语言:javascript
复制
同步方法开始--name:Thread-0
非同步方法开始--name:Thread-1
同步方法结束,name:Thread-0
非同步方法结束,name:Thread-1
All Finished

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

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

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

代码语言:javascript
复制
/**
 * 作者:张风捷特烈
 * 时间: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());
    }
}
代码语言:javascript
复制
sleep3msSync1开始--name:Thread-0
sleep3msSync1结束,name:Thread-0
sleep3msSync2方法开始--name:Thread-1
sleep3msSync2结束,name:Thread-1
All Finished

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

不测试都知道:一个是class锁,一个是this锁,锁不同,不生效 在第5个的基础上加上static关键字,其余不变,结果不出所料

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

代码语言:javascript
复制
public static synchronized void sleep3msSync2() {
代码语言:javascript
复制
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;//异常

线程1出现异常.png

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

四、Java内存模型(JMM--Java Memory Model)
1.Java内存模型的概念

描述Java程序中各变量(线程共享变量)的访问规则, 即在JVM中将变量存储到内存和从内存中读取变量的底层细节

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

规定:
[1]线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
[2]线程无法直接访问非己方工作内存中的变量,线程间变量值的传递需要间接通过主内存。

JMM--java内存模型.png


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

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

共享变量可见性.png


3、synchronized实现可见性

锁定的线程1所做的任何修改都要在释放锁之前从工作内存刷新到主内存 线程2拿到锁时从主内存中拷贝需要的变量到自己的工作内存(从而实现共享变量的可见)


4、缺陷:
代码语言:javascript
复制
效率低:
锁的释放情况少(只能自动释放,或异常)
不能中断等待锁的线程

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

5.注意点
代码语言:javascript
复制
锁对象不能为空,作用域不宜过大,避免死锁
|---锁对象的信息是放在对象头中,所以不能为空
|---作用域过大,导致串行执行的代码变多,效率下降

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

多线程访问同步方法的几种情况
死锁简单演示
代码语言:javascript
复制
/**
 * 作者:张风捷特烈
 * 时间: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());
        }
    }
}

死锁.png


六、synchronized原理简述
1.定义一个类,其中用一个同步方法
代码语言:javascript
复制
public class Decode {

    private Object obj = new Object();

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

        }
    }

}

2.反编译(含同步方法的类):
代码语言:javascript
复制
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.如果将同步代码块去掉,再反编译
代码语言:javascript
复制
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对象头会有monitorentermonitorexit 注意是加锁时使用的对象obj的对象头


4.monitorentermonitorexit
代码语言:javascript
复制
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的所有者

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.12.29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 零、前言
  • 一、多线程的简单回顾
    • 1.入门级
      • 2.如何让打印在两个线程完成后才调用
        • 2.1:让主线程先睡一会
        • 2.2.join方法
      • 3.结果呢?
        • 3.1下面是十次结果
        • 3.2从中能看出什么?
        • 3.3为什么
      • 3.4:i++发生了什么?
        • 4.怎么解决呢?
        • 二、同步锁
          • 0.测试代码(此时还未同步)
            • 1.对象锁之同步代码块锁
              • 1.1:同步代码块的添加
              • 1.2:运行结果
              • 1.3:锁对象
              • 1.4:多把锁
            • 2.对象锁之普通方法锁
              • 2.1:使用
              • 2.2:打印结果
              • 2.3:如何证明方法锁的锁对象是this
              • 2.4:反证
            • 3.类锁之静态方法锁(static方法+synchronized)
              • 3.1.static方法+synchronized
            • 4.类锁之Class对象锁
              • 5.现在回头来看
              • 三、多线程访问方法的一些情况
                • 1.两个线程访问一个对象的普通同步方法
                  • 2.两个线程访问两个对象的普通同步方法
                    • 3.两个线程访问静态同步方法
                      • 4.两个线程分别访问普通同步方法和非同步方法
                        • 5.两个线程分别访问一个对象的不同普通同步方法
                          • 6.两个线程分别访问静态同步和普通同步方法
                            • 7.抛出异常后,释放锁
                              • 8、synchronized的性质
                              • 四、Java内存模型(JMM--Java Memory Model)
                                • 1.Java内存模型的概念
                                  • 2.如何:线程1的修改被线程2看到
                                    • 3、synchronized实现可见性
                                      • 4、缺陷:
                                        • 5.注意点
                                          • 死锁简单演示
                                      • 六、synchronized原理简述
                                        • 1.定义一个类,其中用一个同步方法
                                          • 2.反编译(含同步方法的类):
                                            • 3.如果将同步代码块去掉,再反编译
                                              • 4.monitorenter和monitorexit
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档