JAVA并发编程3_线程同步之synchronized关键字

在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题。

不安全线程分析

public class Test implements Runnable {private int i = 0;private int getNext() {return i++;}@Overridepublic void run() { // synchronizedwhile (true) {synchronized(this){if(i<10){System.out.println(getNext());}elsebreak;}}}public static void main(String[] args) {Test t = new Test();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();Thread.yield();}
}


与之前的代码的区别在于run方法被synchronized关键字修饰。

根据上一篇博客的分析:多线程在访问共享资源的时候由于CPU轮流给每个任务分配其占用的时间,而CPU的调度是随机的,因此就会发生某个线程正在访问该变量的时候CPU却将时间片分发给了其他的线程,这样就会发生这样的现象:一个线程从主内存读取到某个变量的值还没来得及修改(或者修改后刷新主内存),另一个线程就获得了CPU的执行权,也从主内存读取改变量的值。当CPU执行权再次回到第一个线程的时候会接着之前的中断处执行(修改变量等),执行权回到第二个线程时却不能看到第一个线程中改变了的值。归结起来就是说违背了线程内存的可见性。避免上看起来产生第一种输出的可能顺序如下图所示(实际上可能的情况非常多,因为i++不是单个的原子操作):


i++对应下面的JVM指令,因此在期间另一个线程都可能会修改这个变量。

4: aload_0

5: iconst_0

6: putfield      #2                  // Field i:I

为了体现内存的可见性,synchronized关键字能使它保护的代码以串行的方式来访问(同一时刻只能由一个线程访问)。保证某个线程以一种可预测的方式来查看另一个线程的执行结果。

线程同步

JAVA提供的锁机制包括同步代码块和同步方法。

每个Java对象都可以用做一个实现同步的锁,这些所成为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock),一个线程进入同步带吗快之前会自动获得锁,并且推出同步带吗快时自动释放锁。获得内置锁的位移途径就是进入由这个锁保护的同步代码块或方法并且该锁还未被其他线程获得。

Java内置锁相当于互斥体(互斥锁),意味着最多有一个线程持有这种锁。当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果线程B永远不释放锁,那么线程A将永远等待下去。

每次只能有一个线程执行内置锁保护的代码块,因此这个锁保护的同步代码块会以原子方式执行,多个线程在执行该代码块时也不会相互干扰。

原子性的含义:一组语句作为一个不可分割的单元被执行。任何一个执行同步代码块的线程,都不可能看到有其他线程正在执行由同一个锁保护的同步代码块。

千万注意:并不是说synchronized代码块或者synchronized方法是不可分割的整体,是原子的,因为,显然使用不同锁的话之间不存在互斥关系。

买票例子的引入

下面是模拟火车站卖票的程序,理论上是要将编号为1-10的票卖按照由大到小顺序卖出去,结果用两个窗口(线程)卖就出现了这样的结果,有些编号的票卖了两次,有些没卖出去,并且还有编号为0的票卖了出去。显然结果错误的。

public class Test implements Runnable {private int i = 10;private void sale(){while (true) {if (i >0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "正在卖第" + i + "张票");i--;} else
break;}}@Overridepublic void run() {sale();}public static void main(String[] args) {Test t = new Test();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();Thread.yield();}
}

出现这种结果的原因就是没有对多个线程共同访问的资源进行同步加锁。下面我们对其进行线程同步,达到想要的效果:

synchronized代码块:

synchronized (lock){//同步的代码}

lock必须是一个引用类型的变量。

使用synchronized同步代码块:

public class Test implements Runnable {private int i = 10;private void sale(){Object o = new Object();while (true) {synchronized(o){if (i >0){System.out.println(Thread.currentThread() + "正在卖第" + i + "张票");i--;}else
break;			}}}@Overridepublic void run() {sale();}public static void main(String[] args) {Test t = new Test();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();Thread.yield();}
} 


咦?使用了同步代码块了怎么结果还是不对呢??我们先看正确的同步:

public class Test implements Runnable {private int i = 10;Object o = new Object();// 通常使用:/*static*/ byte[] lock = new byte[0];private void sale(){while (true) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}synchronized(o){if (i >0){System.out.println(Thread.currentThread() + "正在卖第" + i + "张票");i--;}elsebreak;}}}@Overridepublic void run() {sale();}public static void main(String[] args) {Test t = new Test();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();Thread.yield();}

这里线程同步的原理是怎样的呢?因为任何一个Java对象都可以作为一个同步锁,上面代码的对象o就是一个同步锁。

一个线程执行到synchronized代码块,线程尝试给同步锁上锁,如果同步锁已经被锁,则线程不能获取到锁,线程就被阻塞;如果同步锁没被锁,则线程将同步锁上锁,并且持有该锁,然后执行代码块;代码块正常执行结束或者非正常结束,同步锁都将解锁。

所以线程执行同步代码块时,持有该同步锁。其他线程不能获取锁,就不能进入同步代码块(前提是使用同一把锁),只能等待锁被释放。

这时候回头看上上段代码中的同步代码块,由于两个线程使用的锁是不一样的(创建了两个对象),因此,就算线程A在执行同步代码块,当线程2获得CPU执行权时,检查到这个锁并未被其他线程锁定,因此不具有互斥性,不能达到线程同步的效果。

同步方法

将synchronized作为关键字修饰类的某个方法,这样该方法就变成了同步方法。

直接将sale函数改为synchronized方法的结果是虽然卖票不会乱序,但是只有一个线程在卖票。所以稍微做些调整:

public class Test implements Runnable {private int i = 10;private void sale(){while (true) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}f();}}private synchronized void f(){if (i >0){System.out.println(Thread.currentThread() + "正在卖第" + i + "张票");i--;}elsereturn;}@Overridepublic void run() {sale();}public static void main(String[] args) {Test t = new Test();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();Thread.yield();}
}

这时候的锁是哪个对象呢?

当修饰的方法是类方法时同步锁是该类对应的Class对象;

当修饰普通方法时,该同步锁是当前对象即this。

体会:不要滥用synchronized方法

在平时的编程中为了达到线程同步的目的,在不经认真思考的情况下,经常发生synchronized关键字的滥用,归根结底是没有理解同步的原理本质。

看下面的代码:

public class Test implements Runnable{     @Override  public void run() {  f();}  public synchronized void f(){  System.out.println(this);}public static void main(String[] args) {  Test t1=new Test();  Test t2=new Test();  // f()里面的代码无法达到同步的目的new Thread(t1).start();  new Thread(t2).start();  }  
}
//Output
//Test@2073b879
//Test@d542094

根据打印的结果也可以看出来函数f()是无法同步的,因为这两个线程使用了两个同步锁。这就告诉我们,并不要看到一个方法是synchronized的就想当然的认为它是同步方法就在不同的线程里随便调用。


注:上面的代码里面多次使用到了Thread.sleep(long)方法,是让当前线程睡眠一会,这个方法会让当前线程放弃CPU的执行权,处于Time Waiting状态,CPU不在为其分配时间片。由于机器的不同可能不容易出现我们期望的线程切换,目这样做就可以强制的让线程切换。

另外,在synchronized代码里面使用sleep无效。因为该线程sleep后CPU不在为其分配时间片,但是这个时候线程已经拿到了同步锁,即使睡到天荒地老,它也不会把同步锁交出去,别的线程得到了CPU执行却却苦于没有同步锁而被拒之门外。后面学习线程的状态会讲到这些。


会写代码不一定理解了,理解了不一定能给别人讲清楚。想把一个东西用文字表述清楚真的挺不容易。

转载于:https://www.cnblogs.com/qhyuan1992/p/5385310.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/492247.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习技术发展趋势浅析

来源&#xff1a;中国信息通信研究院CAICT当前&#xff0c;人工智能发展借助深度学习技术突破得到了全面关注和助力推动&#xff0c;各国政府高度重视、资本热潮仍在加码&#xff0c;各界对其成为发展热点也达成了共识。本文旨在分析深度学习技术现状&#xff0c;研判深度学习发…

(含Matlab源码)算术编码(arithmetic coding)的underflow问题

0、文章结构 文章的行文逻辑如下&#xff0c;看官可以根据需要跳读&#xff0c;节省时间。 1、介绍underflow和overflow. 2、underflow问题起源 3、underflow问起探索 4、underflow和overflow的常见情形 5、处理一些溢出问题的小技巧 6、对其中的两种小技巧的优缺点比较…

人类与AI结合的最佳形态是什么样?|A16Z内部万字报告

来源&#xff1a;A16Z合伙人Frank Chen2017年7月&#xff0c;我发布了一个关于人工智能、机器学习和深度学习的入门视频。从那以后&#xff0c;我一直痴迷于阅读关于机器学习的报道。一般来说&#xff0c;你会在媒体的头版上看到两类报道。一类报道的标题是“机器人来抢你的工作…

(Matlab源码)Matlab实现算术编码(Arithmetic coding)超级详解(每一段代码都可以看懂)

1、代码功能 输入&#xff1a;一个字符串输出&#xff1a; codeword&#xff08;码值&#xff09; codeword所占的位数 2、代码框图 3、代码超详解 统计字符串中的字符种类&#xff0c;调用函数&#xff0c;放入数组b中。 bunique(str1); 统计每种字符的个数&#xff0c;放入…

(含Python源码)Python实现K阶多项式的5种回归算法(regression)

0、文章结构 为了方便客官根据需要取阅&#xff0c;节约时间&#xff0c;文章目录结构如下&#xff1a; 问题描述理论部分&#xff1a;五种回归算法两种Python读取文件的方法Python实现五种回归算法使用的工具箱总结 1、问题描述 K阶多项式表达式 其中&#xff0c; 现有数据…

首张人类黑洞照片的背后

摘要&#xff1a;沈海军&#xff1a;今天&#xff08;2019年4月10日&#xff09;下午接受广东卫视采访&#xff0c;就晚上21:00即将发布的人类首张黑洞照片发表了评论。提笔撰稿时&#xff0c;尚未到照片官方的发布时间&#xff0c;故不能一睹黑洞照片的芳容&#xff0c;但鉴于…

一场“交通进化”将至: 5G带给车联网与自动驾驶哪些升级?

来源 &#xff1a;腾讯科技作者&#xff1a;李俨 美国高通公司技术标准高级总监5G时代已经来临&#xff0c;走向商用的步伐也在逐渐加快。腾讯科技联合优质科技CP以及行业专家推出“5G局中局”系列文章&#xff0c;为你解读5G在通讯、物联网、车联网、工业联网、边缘计算、云服…

特斯拉发布Q1无人驾驶安全报告:事故增多 但还是比人类少

来源&#xff1a;聚焦AI的近日&#xff0c;电动汽车制造商特斯拉发布了2019年第一季度自动驾驶仪&#xff08;Autopilot&#xff09;安全性报告&#xff0c;这是特斯拉发布的第三份类似报告&#xff0c;此前该公司认为媒体对特斯拉车辆事故的报道有失公允&#xff0c;因此他们开…

log4net 小记

突然想到想测试一下log4net&#xff0c;结果折腾了两天&#xff0c;才弄出来.....记录下来以备以后查看 背景&#xff1a;vs2013 mvc项目中想体验下log4net的功能&#xff08;主要是文件记录&#xff09; 翻看了log4net的相关资料&#xff0c;才发现其实它是有很多功能的&#…

关于找工作和选专业的思考

个人的成长离不开国家的发展&#xff0c;国家的发展离不开国际的大气候。 国家之间的竞争主要归于经济竞争&#xff0c;经济的核心在于产业&#xff0c;产业的核心则在于科学和技术。 无论是找工作还是选专业&#xff0c;赌的都是对未来趋势的预测&#xff0c;没有人会希望自己…

DARPA 2020财年研发预算 人工智能应用研究投资急剧增长

来源&#xff1a;美国国防部等摘要&#xff1a;2019年3月&#xff0c;特朗普政府公布2020财年预算申请。根据预算法案&#xff0c;2020财年美国国家安全预算总额增加340亿美元&#xff0c;达到7500亿美元&#xff0c;比上年增加5%。美国防部分得的经费为7180亿美元&#xff0c;…

使用Matlab(R2018b)画复杂函数的图形(网格图meshgrid)及等高线contour

1、函数 这里使用2D Michalewicz 函数&#xff0c;其表达式为&#xff1a; 2、画图 2.1 编写2D Michalewicz 函数 f(x,y)(-sin(x).*(sin(x.^2/3.1415926)).^(2*m)...-sin(y).*(sin(2*y.^2/3.1415926)).^(2*m)); 上述代码使用到了Anonymous Functions, 相关变量的数据类型为…

科创板:中国科技产业新引擎

来源&#xff1a;国信研究作者&#xff1a;杨均明&#xff0c;国信证券经济研究所所长万众期待的上海证券交易所科创板即将推出&#xff0c;科创板股票发行审核规则第三条要求&#xff0c;发行人申请股票首次发行上市&#xff0c;应当符合科创板定位&#xff0c;面向世界科技前…

粒子群优化算法(Particle Swarm Optimization)的 Matlab(R2018b)代码实现

这里以 2D Michalewicz function 为对象来演示粒子群算法。 1、Michalewicz function 2、代码详解 2.1 画Michalewicz函数的网格图形 f(x,y)(-sin(x).*(sin(x.^2/3.1415926)).^(2*m)...-sin(y).*(sin(2*y.^2/3.1415926)).^(2*m));range[0 4 0 4]; Ngrid100; dx(range(2)-ran…

中金人工智能报告:AI时代,10 年之后我们还能干什么?|71页完整报告

来源&#xff1a;黑科技智汇中金公司&#xff08;CICC&#xff09;发布了一份长达 71 页的人工智能的证券研究报告《人工智能时代&#xff0c;10 年之后我们还能干什么&#xff1f;》。对全球特别是中国企业当下的人工智能态势作了全面的介绍&#xff1a;包括 BAT、华为、科大讯…

UC伯克利新机器人成果:灵活自由地使用工具

来源&#xff1a;AI 科技评论摘要&#xff1a;前几天我们刚刚介绍了加州大学伯克利分校 Pieter Abbeel 教授领导伯克利机器人学习实验室&#xff08;UC Berkeleys Robot Learning Lab&#xff09;开发的会叠衣服的家务向机器人 BLUE&#xff0c;今天伯克利人工智能实验室&#…

bzoj 1934 最小割

收获&#xff1a; 1、流量为0的边可以不加入。 2、最小割方案要与决策方案对应。 1 #include <cstdio>2 #include <cmath>3 #include <cstring>4 #include <vector>5 #define min(a,b) ((a)<(b)?(a):(b))6 #define oo 0x3f3f3f3f7 #define N 6108 …

工业4.0:数字化供应链的三个方向

来源&#xff1a;资本实验室席卷全球的工业4.0革命几乎影响着每一个行业&#xff0c;包括各行业的供应链管理。在过去&#xff0c;供应链管理是令各行业都非常头疼的环节&#xff0c;因为传统供应链涉及大量模糊且无法访问的数据&#xff0c;就像”黑洞“一样吞噬着成本、时间与…

有效学习

怎样算有效学习&#xff1f; 记得有位大咖说过&#xff0c;‘当你把受过的教育都忘记了&#xff0c;剩下的就是教育’&#xff08;度娘说是爱因斯坦说的&#xff0c;此处未经考证&#xff09;。 学习可以看成是一个把知识放入潜意识的过程。所谓进入潜意识&#xff0c;是指当…

如果机器能帮我们学习,那么有多少东西能够被遗忘?

来源&#xff1a;原理在我还是个学生时的遥远年代&#xff0c;大多数计算机都还是体型巨大的机器。当时我有一个朋友&#xff0c;他的博士导师坚持让他对一个冗长又困难的原子理论进行手写计算。他用掉了一页又一页的草稿纸&#xff0c;上面充满了错误。于是最终他屈服于自己的…