1、 设计线程安全的类
1、找出构成对象状态的所有变量
2、找出约束状态变量的不变性条件
3、建立对象状态的并发访问管理策略
package cn.xf.cp.ch04; /** * *功能:JAVA监视器模式的线程安全计数器 *时间:下午6:22:23 *文件:Counter.java *@author Administrator * */ public class Counter { //只有这一个变量,这个value就是counter的全部状态 private long value = 0; //获取值,单线程操作 private synchronized long getValue() { return value; } public synchronized long increment() { if(value == Long.MAX_VALUE) throw new IllegalStateException("统计数值溢出"); return ++value; } }
2、实例封闭
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
通过封闭与加锁等机制使一个类成为线程安全的
package cn.xf.cp.ch04; public class Person { private String name; private int age; private char sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } }
package cn.xf.cp.ch04; import java.util.HashSet; import java.util.Set; public class PersonSet { private final Set<Person> mySet = new HashSet<Person>(); public synchronized void addPerson(Person p) { mySet.add(p); } public synchronized boolean containsPerson(Person p) { return mySet.contains(p); } }
2、1 Java监视器模式
这个模式的对象会把所有可变状态都封装起来,并由对象自己的内置锁来保护。
package cn.xf.cp.ch04; /** * *功能:私有锁的使用 *时间:下午9:18:01 *文件:PrivateLock.java *@author Administrator * */ public class PrivateLock { //使用私有变量作为对象锁 private final Object myLock = new Object(); private int i; public void someMethod() { synchronized(myLock) { //这里对状态量i进行操作 i = 110; System.out.println("私有锁"); } } }
私有的锁对象可以将锁封装起来,使客户代码无法获取锁,但是客户代码可以通过公有方法来访问锁,这样也就可以参与到同步中来。
活跃性的问题:死锁, 饥饿和活锁
饥饿(Starvation)
它描述了这样一个场景,当一个线程不能获取定期访问来共享资源而不能继续运行,在共享资源被饥渴线程长期占有时,就会发生饥饿。例如,加速一个对象提供一个要花很长时间才能返回结果的同步方法,如果一个线程频繁调用这个方法,其它线程也需要频繁调用同步进入同一个对象的方法时,阻塞就发生了。
活锁(Livelock)
一个线程经常对另外一个线程的响应做响应的处理,如果线程的另外一个动作同样是对另外一个线程响应而发生,结果可能导致活锁,类似死锁,发生活锁的线程不能更进一步的处理,然而,线程并没有被阻塞,他们只是忙碌于响应对方而重复工作,这类似于两个人在走廊中试图让对方,a移向左以让b通过,而b移向右以让a通过,可以看到,他们仍然互相阻塞,a移动到右边,而b移动到左边,结果他们仍然还是阻塞着。。。
如果不使用私有锁:那么可能客户代码错误地获取另一个对象的锁,可能会产生活跃性的问题(一个并发应用能够及时执行任务的特性称为活跃性)。此外想要验证某个公有访问的锁在程序中是否被正确地使用,则需要检查整个程序,而不是单个类。
2、2 示例:车辆追踪
车辆标识和位置点,类型
package cn.xf.cp.ch04; public class MutablePoint { private int x, y; public MutablePoint() { x = 0; y = 0; } public MutablePoint(MutablePoint p) { this.x = p.getX(); this.y = p.getY(); } public synchronized int getX() { return x; } public synchronized void setX(int x) { this.x = x; } public synchronized int getY() { return y; } public synchronized void setY(int y) { this.y = y; } }
车辆追踪类
package cn.xf.cp.ch04; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * *功能:基于监视器模式的车辆追踪 *时间:下午9:50:12 *文件:MonitorVehicleTracker.java *@author Administrator * */ public class MonitorVehicleTracker { private final Map<String, MutablePoint> locations; //构造函数对map进行初始化 public MonitorVehicleTracker(Map<String, MutablePoint> locations) { //为了保持内部数据不会发生逸出,进行深拷贝 this.locations = deepCopy(locations); } public synchronized Map<String, MutablePoint> getLocations() { return deepCopy(locations); } public synchronized MutablePoint getLocation(String id) { //获取要返回的数据 MutablePoint loc = locations.get(id); //创建一个新的对象返回 return loc == null ? null : new MutablePoint(loc); } public synchronized void setLocation(String id, int x, int y) { MutablePoint loc = locations.get(id); if(loc == null) { //如果没有这个对象的话,抛出异常,还是重新创建,看个人业务 throw new IllegalArgumentException("没有这个id:" + id); } loc.setX(x); loc.setY(y); } private static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) { //创建一个新的接受数据对象 Map<String, MutablePoint> result = new HashMap<String, MutablePoint>(); //循环取数据 for(String id : m.keySet()) { //根据key取值 result.put(id, m.get(id)); } //返回数据,返回指定映射的不可修改视图。 //此方法允许模块为用户提供对内部映射的“只读”访问 return Collections.unmodifiableMap(result); } }