经历了两个月的沉淀,感觉整体能力有所提升,最近除了 年终总结
也没有什么打算了
高并发这块一致是我的心病,在这年尾,抽刀,奋力一击吧
虽然会用线程,但是总感觉有很多地方让我挺烦心,比如并发和那两个关键字
曾经三次想要突破掉多线程,但都失败了,只好暂时离开,现在的我感觉应该可以了
本文按照慕课网免费课程敲的,同时也加入了我大量的思考和绘图,希望对你有所帮助
下面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方法要慢一些
两个方法:1) 让主线程先睡一会
、2) 使用线程对象的join方法
总之就是推迟 System.out.println(i);
的执行时间
这个方法很容易想到,但睡多久不好把握,一般小测试1s应该够了
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++; } } } 复制代码
正规的还是用join吧,他会让该线程先行,使以 System.out.println(i);
被推后
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++; } } } 复制代码
137355 、 114412 、115381 、128482 、151021 、 109093 、 128610 、128144 、122390 、123746 复制代码
1).每次执行结果都不一样
2).它们都大于100000且小于200000
理论上两个线程,每个线程加100000次,一共应该200000才对
想象一下这个场景:
有两个神枪手对着靶子打(必中),每把枪有100000颗软子弹 靶子有感应器和计数器,当软子弹接触靶子那一刻,计数器加1 当两个子弹同时接触时,感应器有无法及时反应,只会被记录一次,即计数器只+1 然后两人疯狂扫射,最后看靶子上计数器最终的数值 可想而知最后计数器上的数应该是小于200000的,所以代码中也类似 两个线程便是神枪手,run的时候开始同时疯狂扫射,i便是靶子 复制代码
内存中读取i的值
,2)
i=i+1
,3)
将结果写回内存
i=9时,若线程2已经在第三步了,但还没写入内存。这时线程1进入,读出i的值仍是9,
从而导致此次结束两个结果都是10,这就是为什么达不到200000的原因
这就相当于两个神枪手同时开枪,靶子未及时反应而导致两颗同弹
先看问题出在哪,是两个人同时开枪对一个靶子
一个人是不能在同一时刻发出两法子弹的,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++; } } } 复制代码
先看一下干了什么事:线程创建不说了,run方法中:
打印信息
--> 当前线程睡三秒
--> 打印运行结束
(如下图)
根据时间线可以看出来打印结果(可以看出两个人一起睡了,这还得了...)
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先睡,线程2进来也睡了,能忍吗?不能忍!
快把哥的四十米大刀,不对,是大锁拿来,在我睡觉前先把门锁上
线程1进来睡,然后把门锁上,线程2就进不来,只能等线程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才能进来睡
对象锁,代码块形式--name:Thread-0 运行结束,name:Thread-0 对象锁,代码块形式--name:Thread-1 运行结束,name:Thread-1 All Finished 复制代码
等等,这this是什么鬼?--有点基础的都知道是当前类对象
System.out.println(this);// top.toly.并发.SynObj_Block@77c89a74 同步代码块synchronized()接收一个对象,该对象可任意指定: Object lock = new Object(); synchronized (lock) {//TODO} 新建一个空对象也可以 复制代码
也会你会说:既然随便一个对象都可以当做锁对象,Java自己给内置个呗
还传个参数,累不累人。等等,存在即合理,且看下面...
想一下如果一个房间两张床,你上来把门锁了,岂不是不合理?
那该怎么办?两扇门,两把不同的锁呗(就像两个人合租一间大房子一样)
你可以根据图中 时间线
好好想想(画个图也不是那么容易的...且看且珍惜)
/** * 作者:张风捷特烈 * 时间: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秒,这也就两个线程而已
如果百万级的线程数,哪怕微小的效率提升都是有价值的
正如1.4所想:我就是想简单的加个锁,每次同步代码块还有传个对象,挺烦的
所以有一个叫方法锁,什么对象每个类都有?答案: this
,方法锁的对象默认是this
/** * 作者:张风捷特烈 * 时间: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()); } } 复制代码
和同步代码块一致
方法锁测试--name:Thread-0 结束,name:Thread-0 方法锁测试--name:Thread-1 结束,name:Thread-1 All Finished 复制代码
@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锁是一把锁,也就是只有一扇门,不须一个一个睡
@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
说是类锁,实质上是使用了Class对象当做锁,非要较真的话,你可以把他看作对象锁
Class对象有什么特点:一个类可以有多个对象,但 仅有一个
Class对象
这就可以导致:类锁只能在同一时刻被一个对象拥有
普通方法+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的本意:静态,具有全局控制力
相当于把 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 复制代码
synchronized: 同步的
官宣: 同步方法支持一种简单的策略来[防止线程干扰]和[内存一致性错误]: 如果一个对象变量对多个线程可见,则对它的所有读写都是通过同步方法完成的 民宣: 保证同一时刻最多只一个线程执行该段代码,来保证并发安全 复制代码
感觉有点...麻烦
1.两个线程访问一个对象的普通同步方法 2.两个线程访问两个对象的普通同步方法 3.两个线程访问静态同步方法 4.两个线程分别访问普通同步方法和非同步方法 5.两个线程分别访问一个对象的不同普通同步方法 6.两个线程分别访问静态同步和非静态同步方法 方法抛出异常后,会释放锁 复制代码
二-->2
中的例子: 线程1,2
访问一个对象 instance
的同步方法: sleep3ms
同一个对象,需要等待锁的释放,才能进入普通同步方法
二-->3-->3.1
中第一个小例子(用脚趾头想的那个)
同一类的两个不同对象的普通同步方法,对于两个线程而言,同步是无用的
二-->3-->3.1
第二个小例子,轻轻加了个static
由于静态同步方法的锁是class,锁对该类的所有对象都有效
线程2的方法没加锁(非同步方法),就进来睡了呗,也没什么特别的
注意两头几乎同时执行,测试了几次,两头的先后顺序不定
/** * 作者:张风捷特烈 * 时间: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 复制代码
由于普通同步方法是this锁,所以对不同普通同步方法锁是一致的,都生效
/** * 作者:张风捷特烈 * 时间: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 复制代码
不测试都知道:一个是class锁,一个是this锁,锁不同,不生效
在第5个的基础上加上static关键字,其余不变,结果不出所料
public static synchronized void sleep3msSync2() { 复制代码
sleep3msSync1开始--name:Thread-0 sleep3msSync2方法开始--name:Thread-1 sleep3msSync2结束,name:Thread-1 sleep3msSync1结束,name:Thread-0 All Finished 复制代码
可以看出线程1抛异常后,线程2是可以正常运行的(说明线程1的锁已经被释放)
就像线程1在睡觉,睡着睡着仙逝了,房东(JVM)会把它抬走,把锁给下一个人,继续睡...
在第5个的代码上稍微修改: int a=1/0;//异常
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 复制代码
一把锁只能由一个线程获取,没拿到锁的线程必须等待
不同的锁之间互不影响(相当于进不同的门,互不干扰,无需等待)
无论正常执行还是抛出异常,都会释放锁
可重入:同一线程外层函数获取锁之后,内层函数可以直接再次获取该锁 好处:避免死锁,提高封装性 粒度:线程范围 即synchronized修饰的同步方法内部`并非只能`调用同步方法 复制代码
不可中断:比如我线程1要小睡个十万年,那线程2就要在门等上十万年(想走都不行)。 复制代码
描述Java程序中各变量(线程共享变量)的访问规则,
即在JVM中将变量存储到内存和从内存中读取变量的底层细节
1.所有的变量都存储在主内存中, 2.每条线程都有自己独立的工作内存。其保存该线程用到的变量副本(主内存变量拷贝)。 规定: [1]线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。 [2]线程无法直接访问非己方工作内存中的变量,线程间变量值的传递需要间接通过主内存。 复制代码
1.工作内存1操作共享变量a后 刷新到主内存
2.然后线程2从主内存中 读取
共享变量a值并 拷贝
到自己的工作内存
锁定的线程1所做的任何修改都要在释放锁之前从工作内存刷新到主内存
线程2拿到锁时从主内存中拷贝需要的变量到自己的工作内存(从而实现共享变量的可见)
效率低: 锁的释放情况少(只能自动释放,或异常) 不能中断等待锁的线程 不灵活: 加锁和释放锁的时机单一,每个锁只有单一的条件 无法知道释放成功获取锁 复制代码
锁对象不能为空,作用域不宜过大,避免死锁 |---锁对象的信息是放在对象头中,所以不能为空 |---作用域过大,导致串行执行的代码变多,效率下降 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()); } } } 复制代码
public class Decode { private Object obj = new Object(); public void say(Thread thread) { synchronized (obj){ } } } 复制代码
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" 复制代码
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的对象头
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的所有者 复制代码
更深的东西以后慢慢来吧,先了解个线程同步的大概,并发的内功也不是一朝一夕能成的
项目源码 | 日期 | 备注 |
---|---|---|
V0.1 | 2018-12-29 | 燃烧吧!我的并发之魂--synchronized |
笔名 | 微信 | 爱好 | |
---|---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 | 语言 |
我的github | 我的简书 | 我的掘金 | 个人网站 |
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持