线程封闭:
当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭。它是实现线程安全最简单的方式之一。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性。即使被封闭的对象本身不是线程安全的。
1、栈封闭
栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。
基本类型的局部变量和引用变量的线程封闭性:
public int loadTheArk(Collection<Animal> candidates){SortedSet<Animal> animals;int numPairs = 0;Animal candidate = null;//animals被封闭在方法中,不要使它逸出animals = new TreeSet<Animal>(new SpeciesGenderComparator());animals.addAll(candidates);for(Animal a: animals){if(candidates == null || !candidate.isPotentialMate(a))candidate = a;else{ark.load(new AnimalPair(candidate, a);++numPairs;candidate = null;}}return numPairs;
}
上诉代码中,基本类型的局部变量numPairs被封装在线程内,其他线程无论如何都访问不到该变量。
在方法中实例化一个TreeSet对象,并将指向该对象的一个引用保存在animals中。此时,只有一个引用指向集合animals,这个引用被封闭在局部变量中,因此也被封闭在执行线程中。然而,如果发布了animals, 封闭性就会被破坏。
2、ThreadLocal类
维持线程封闭的另一种更规范的方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal类提供了get/set等方法,这些方法为每一个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的新值。
ThreadLocal对象通常用于防止对可变的但是离变量或全局变量进行共享。
例如,通过将JDBC的连接保存在ThreadLocal中,每个线程都会拥有属于自己的数据库连接。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){public Connection initialValue(){return DriverManager.getConnection(DB_URL);}
};public static Connection getConnection(){return connectionHolder.get();
}
在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:
- 线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,只能由该线程修改;
- 只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不修改它。共享的只读对象包括不可变对象和事实不可变对象。
- 线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步同步。
- 保护对象:被保护的对象只能通过持有特定的锁来访问。
实例封闭:
实例封闭简称“封闭”,当一个对象被封装到另一个对象中时,能够访问被封装对象的所有代码路径都是已知的。与对象可由整个程序访问的情况相比,更易于对代码进行分析。
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
被封闭的对象一定不能超出它们既定的作用域。对象可以封闭在类的一个实例(例如作为类的一个私有成员)中,或者封闭在某个作用域内(例如作为局部变量),在或者封闭在线程内(例如在某个线程中将对象从一个方法传递给另一个方法,而不是在线程间共享该对象)。
下述代码说明了如何通过封闭和加锁机制使一个类成为线程安全的(即使这个类的状态变量不是线程安全的)。PersonSet的状态由HashSet管理,而HashSet并非线程安全的。但由于mySet是私有的并且不会溢出,因此ashSet被封闭在PersonSet中。
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);}
当然,如果将一个本该封闭的对象发布出去,也会破坏封闭性。