【Java学习笔记】17:Single Threaded Execution Pattern

Single Threaded Execution Pattern是指“以一个线程执行的模式”的意思,就像通过某些独木桥时一次只能让一个人走完再走下一个人,否则桥就会断裂(得不到预期的效果)。

下面的例子说明了该使用Single Threaded Execution Pattern而没有使用的范例。

*不使用Single Threaded Execution Pattern的例子

package day17;
//门同一时刻记录过桥的人和总人数
public class Gate {
	private int counter=0;
	private String name="Nobody";
	private String address="Nowhere";
	public void pass(String name,String address)//通过门
	{
		this.counter++;
		this.name=name;
		this.address=address;//记录信息,计数+1
		check();//检查是否正常
	}
	public String toString()
	{
		return "No."+counter+":"+name+","+address;
	}
	private void check()//检查地区和名字第一个字母是否相同,正常条件下应该相同
	{
		if(name.charAt(0)!=address.charAt(0))//如果不同
		{
			System.out.println("***BROKEN***"+toString());//输出错误信息
		}
	}
}

package day17;
//UserThread类表示不断穿越门的行人
public class UserThread extends Thread {
	private final Gate gate;
	private final String myname;
	private final String myaddress;
	public UserThread(Gate gate,String myname,String myaddress)//构造器
	{
		this.gate=gate;
		this.myname=myname;
		this.myaddress=myaddress;
	}
	public void run()//线程的启动
	{
		System.out.println(myname+" BEGIN");
		while(true)
		{
			gate.pass(myname, myaddress);
		}
	}
}

package day17;

public class Main {

	public static void main(String[] args) {
		System.out.println("Testing Gate,hit CTRL+C to exit.");
		Gate gate=new Gate();//共用一个门
		new UserThread(gate,"Alice","Alaska").start();
		new UserThread(gate,"Bobby","Brazil").start();
		new UserThread(gate,"Chris","Canada").start();
	}
}

一种运行结果:

Testing Gate,hit CTRL+C to exit.

Bobby BEGIN

Alice BEGIN

***BROKEN***No.134312:Alice,Alaska

***BROKEN***No.1163058:Bobby,Brazil

Chris BEGIN

 

可以看到这个Gate类在多线程下无法正常执行,说明它不是线程安全(thread-safe)的类。

从运行结果中可以看出第一次出错时counter的值是134312,也就是说发生第一个错误已经有十几万人通过了。如果这里不是用无穷循环,只是简单的几次几万次测试可能都找不到错误,这就是多线程设计中一个比较困难的地方——测试找不到错误也不代表程序是安全的。测试次数不够、时间点不对,就可能检查不到问题。

在错误信息中,发现A和A明明是一样的却出错了,这是因为线程在执行check方法时出错,但是其它线程正不断调用pass方法,name和address字段的值又被修改了所以显示的也不是出错的那个值。

出错的原因是线程调用pass和check方法时语句的交错执行产生的。也就是说有两个线程在比赛,赢的一方将先把值改写。这样引发竞争(race)的状况,称为race condition。

 

Single Threaded Execution Pattern中,有SharedRecource(共享资源)的出现。共享资源将会拥又一些方法,又分为safeMethod(多个线程同时调用也不会出问题)和unsafeMethod(多个线程同时调用会出问题),只要对后者加以防卫,将其定义成synchronized方法,就可以实现这个目标。

 

*修改Gate类为线程安全的类(只加了两个关键字)

package day17;
//门同一时刻记录过桥的人和总人数
public class Gate {
	private int counter=0;
	private String name="Nobody";
	private String address="Nowhere";
	public synchronized void pass(String name,String address)//通过门
	{
		this.counter++;
		this.name=name;
		this.address=address;//记录信息,计数+1
		check();//检查是否正常
	}
	public String toString()
	{
		return "No."+counter+":"+name+","+address;
	}
	private synchronized void check()//检查地区和名字第一个字母是否相同,正常条件下应该相同
	{
		if(name.charAt(0)!=address.charAt(0))//如果不同
		{
			System.out.println("***BROKEN***"+toString());//输出错误信息
		}
	}
}

这个必须只让单线程执行的程序范围,称为临界区。

这两个方法也就能保证同时只有一个线程可以执行它了。会需要用到Single Threaded Execution Pattern的情况是在共享资源的实例可能同时被多个线程访问的时候。就算是多线程程序,如果所有线程完全独立有哪些,那也没有使用Single Threaded Execution Pattern的必要,这个状态成为线程互不干涉(interfere)。另外,如果共享资源的实例创建了以后,从此不会再改变状态,也没有使用Single Threaded Execution Pattern的必要。

 

死锁

使用Single Threaded Execution Pattern时可能会有发生死锁(deadlock)的危险。死锁是两个线程分别获取了锁定,互相等待另一个线程解除锁定的现象。

Single Threaded Execution达到下面这些条件时,可能会出现死锁的现象:

①具有多个共享资源的参与者

②线程锁定一个共享资源时,还没解除就去锁定另一个共享资源

③获取共享资源参与者的顺序不固定

 

可重用性和继承异常

当一个类的共享资源开放给其子类访问时,撰写子类的人可能写出没有防卫的unsafeMethod,这样这些共享资源在子类化时丧失了安全性。也就是说包括子类在内,若非所有unsafeMethod都定义成synchronized,就无法保证SharedResource参与者的安全性。

如此继承引起的问题,通常称为继承异常(inheritance anomaly)。

 

Single Threaded Execution Pattern使程序执行性能低落的原因

①获取锁定要花时间

进入synchronized方法时,要获取对象的锁定,这个操作要花些时间。

②线程冲突时必须等待

当线程执行临界区内的操作时,其他要进入临界区的线程会被阻挡。称为冲突(conflict)。因此要尽量缩小临界区范围。

 

近期笔记参考自:中国铁道出版社《Java多线程设计模式》。





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值