燃烧吧!我的并发之魂——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
* 邮箱:[email protected]
* 说明:对象锁--代码块锁
*/
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
* 邮箱:[email protected]
* 说明:对象锁--普通方法锁
*/
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
* 邮箱:[email protected]
* 说明:对象锁--静态方法锁
*/
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
* 邮箱:[email protected]
* 说明:对象锁--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
* 邮箱:[email protected]
* 说明:两个线程分别访问普通同步方法和非同步方法
*/
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
* 邮箱:[email protected]
* 说明:两个线程分别访问一个对象的不同普通同步方法
*/
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
* 邮箱:[email protected]
* 说明:死锁简单演示
*/
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."":()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
#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 // "":()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."":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."":()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 7: aload_2
8: monitorexit 9: goto 17
12: astore_3

13: aload_2
14: 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."
":()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
#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 // "":()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."":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."":()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. monitorenter 和 monitorexit

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的所有者
复制代码

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

其实很多技术都不是一两篇文章就能说的清楚的。

那如何快速有效的学习并精通呢?

说到这里给大家免费分享一波福利吧!我自己收集了一些Java资料,里面就包涵了一些BAT面试资料,以及一些 Java 高并发、分布式、微服务、高性能、源码分析、JVM等技术资料

转发!

转发!

转发后关注我私信“架构”

以下价值1990元资料,免费领取!


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



分享到:


相關文章: