Java开发笔记(一百)线程同步synchronized
2019-05-22 06:33:48来源:博客园 阅读 ()
多个线程一起办事固然能够加快处理速度,但是也带来一个问题:两个线程同时争抢某个资源时该怎么办?看来资源共享的另一面便是资源冲突,正所谓鱼与熊掌不可兼得,系统岂能让多线程这项技术专占好处?果然是有利必有弊,且看之前演示售票任务时候的多线程操作,具体代码如下所示:
// 多个线程同时操作某个资源,可能会产生冲突 private static void testConflict() { // 创建一个售票任务 Runnable seller = new Runnable() { private Integer ticketCount = 100; // 可出售的车票数量 @Override public void run() { while (ticketCount > 0) { // 还有余票可供出售 ticketCount--; // 余票数量减一 // 以下打印售票日志,包括售票时间、售票线程、当前余票等信息 // 为更好地重现资源冲突情况,下面尽量拉大访问ticketCount的时间间隔 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); String dateTime = sdf.format(new Date()); String desc = String.format("%s %s 当前余票为%d张", dateTime, Thread.currentThread().getName(), ticketCount); System.out.println(desc); } } }; new Thread(seller, "售票线程A").start(); // 启动售票线程A new Thread(seller, "售票线程B").start(); // 启动售票线程B new Thread(seller, "售票线程C").start(); // 启动售票线程C }
光光看代码感觉并无不妥之处,仅仅是起了三个售票线程共同卖票呗,这能有什么问题?!倘若只运行一次售票代码,倒也看不出什么名堂,可是一旦反复地多次运行这段售票代码,那么总会出现类似下列日志的意外情况,特别是在系统资源比较繁忙的时刻:
10:56:38.182 售票线程A 当前余票为97张 10:56:38.182 售票线程B 当前余票为97张 10:56:38.182 售票线程C 当前余票为97张 10:56:38.186 售票线程B 当前余票为95张 10:56:38.186 售票线程A 当前余票为95张 10:56:38.186 售票线程C 当前余票为93张 ………………………这里省略余下的日志……………………
我的天,售票日志竟然打印出了相同的余票数量,这正是多线程并发造成的结果。因为在ticketCount的自减语句和后面的日志打印语句中间还有其它代码,每行代码都需要消耗一点点的时间,哪怕是零点几毫秒,但就在这一瞬间,余票可能又被别的线程卖掉了一张,所以等到线程A打印余票日志之时,ticketCount早已被卖了不止一次。如此一来,日志打印前后的余票数量遇到不一致的情况,也就不足为奇了。
问题的症结在于余票变量ticketCount是动态变化着的,三个售票线程争先恐后地卖票,故而任一时刻的余票数量都可能发生改变。解决问题的要点自然落在余票的管控上面,正好Java提供了一个名叫synchronized的关键字,它可用来修饰某个方法或者某块代码,目的是限定该方法/代码块为同步方法/同步代码块,也就是规定同一时刻只能有一个线程执行同步方法,其它线程来了以后必须在旁边等待,直到先来的线程跑完同步方法,其它线程方可依次排队执行该同步方法。
回到之前的售票代码,第一反应是能否把售票任务的run方法设置为同步方法?与其瞎猜测,不如试试再说,于是给run方法加上关键字synchronized之后的代码片段如下所示:
// 指定整个run方法为同步方法,这样同一时刻只允许一个线程执行该方法 public synchronized void run() { while (ticketCount > 0) { // 还有余票可供出售 ticketCount--; // 余票数量减一 // 以下打印售票日志,包括售票时间、售票线程、当前余票等信息 String left = String.format("当前余票为%d张", ticketCount); PrintUtils.print(Thread.currentThread().getName(), left); } }
添加完毕再次运行售票代码,观察到了以下的售票日志:
22:46:06.733 售票线程A 当前余票为99张 22:46:06.734 售票线程A 当前余票为98张 22:46:06.735 售票线程A 当前余票为97张 22:46:06.735 售票线程A 当前余票为96张 ………………………这里省略余下的日志……………………
可见现在只剩线程A在兀自卖票,而线程B和线程C呆在一旁陪太子读书。原来synchronized给整个run方法加锁,那么只要线程A尚未结束运行,线程B和线程C就都不允许置身其中,结果便退化为只有一个线程在售票了。显然给run方法添加synchronized的做法管得太多了,其实仅有ticketCount这个余票变量会引起资源冲突,因此不妨缩小synchronized的管辖面,单单把余票减一的代码通过synchronized加以限定,并定义一个局部变量count来保存减一后的余票数值。重新修改后的售票代码片段示例如下:
public void run() { while (ticketCount > 0) { // 还有余票可供出售 int count; // 指定某个代码块为同步代码块,这样同一时刻只允许一个线程执行该段代码 synchronized (this) { count = --ticketCount; // 余票数量减一 } // 以下打印售票日志,包括售票时间、售票线程、当前余票等信息 String left = String.format("当前余票为%d张", count); PrintUtils.print(Thread.currentThread().getName(), left); } }
多次运行修改后的售票代码,观察到的售票日志终于正常打印余票数量了:
16:33:10.265 售票线程A 当前余票为99张 16:33:10.265 售票线程C 当前余票为97张 16:33:10.265 售票线程B 当前余票为98张 16:33:10.266 售票线程A 当前余票为96张 16:33:10.266 售票线程B 当前余票为94张 16:33:10.266 售票线程C 当前余票为95张 ………………………这里省略余下的日志……………………
注意到上述的同步代码块把余票数量赋值给一个局部变量,仿佛某个带返回值的方法,既然这块代码的形式与方法相像,干脆提取出来作为独立的同步方法,于是优化后的售票代码变成了下面这般:
// 把操作共享资源的代码单独提取出来作为同步方法 private static void testSyncMinMethod() { // 创建一个售票任务 Runnable seller = new Runnable() { private Integer ticketCount = 100; // 可出售的车票数量 @Override public void run() { while (ticketCount > 0) { // 还有余票可供出售 // 获得减一后的余票数量。注意getDecreaseCount是个同步方法 int count = getDecreaseCount(); // 以下打印售票日志,包括售票时间、售票线程、当前余票等信息 String left = String.format("当前余票为%d张", count); PrintUtils.print(Thread.currentThread().getName(), left); } } // 将余票数量减一,并返回减后的余票数量 private synchronized int getDecreaseCount() { return --ticketCount; // 余票数量减一 } }; new Thread(seller, "售票线程A").start(); // 启动售票线程A new Thread(seller, "售票线程B").start(); // 启动售票线程B new Thread(seller, "售票线程C").start(); // 启动售票线程C }
以上代码同样有效避免了售票之时的资源冲突,并且代码的组织结构更加清晰明了。
更多Java技术文章参见《Java开发笔记(序)章节目录》
原文链接:https://www.cnblogs.com/pinlantu/p/10896787.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:集合遍历
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash