Java开发笔记(一百)线程同步synchronized

多个线程一块儿办事当然可以加快处理速度,可是也带来一个问题:两个线程同时争抢某个资源时该怎么办?看来资源共享的另外一面即是资源冲突,正所谓鱼与熊掌不可兼得,系统岂能让多线程这项技术专占好处?果真是有利必有弊,且看以前演示售票任务时候的多线程操做,具体代码以下所示:html

	// 多个线程同时操做某个资源,可能会产生冲突
	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);
				}
			}

 

添加完毕再次运行售票代码,观察到了如下的售票日志:ide

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);
				}
			}

 

屡次运行修改后的售票代码,观察到的售票日志终于正常打印余票数量了:this

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开发笔记(序)章节目录orm

相关文章
相关标签/搜索