集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。文章主要分析jdk1.5的3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深度项目开发中获益非浅。
通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。
采用静态ConcurrentHashMap处理缓存:http://blog.csdn.net/dreamthen/article/details/14125245
建立一个bean:
@LocalBean
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class LoanBridge {
private final ConcurrentHashMap<String, LoanExt> finishedLoans = new ConcurrentHashMap<>();
//初始化finishedLoans
@PostConstruct
public void init() {
}
}
被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行执行之后执行
项目启动时此bean会默认加载,这样其他类调用此bean时可直接用finishedLoans
如果一个session bean被定义成@Singleton的话,是由EJB容器,也就是glassfish在启动的时候去进行初始化的,仅维护一个实例,所以像ApplicationBean用到全局变量,还有一些有定时任务的bean,都被@Singleton了。ConcurrencyManagementType.BEAN是由开发者去管理并发控制,所以可以看到这些bean有许多方法被 synchronized修饰。
@Asynchronous
private synchronized void updateScheduledAndOpen() {
}
ejb中通过锁机制也可以达到这种效果:
@LocalBean
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class CorporationBridge {
@EJB
ApplicationBean appBean;
@EJB
CorporationUserService corporationService;
private Set<String> legalPersons = new HashSet<>();
@PostConstruct
void init() {
triggerUpdate();
}
@Lock(LockType.WRITE)
public void triggerUpdate() {
legalPersons.clear();
legalPersons = new HashSet<>(corporationService.listLegalPerson(appBean.getClientCode()));
}
以下供参考:ConcurrencyManagementType的两种类型注释,Container与Bean
Even though Stateless EJB's may never be concurrently accessed by more than a single client call, this article would be otherwise incomplete if it did not mention this kind of EJB's.
Stateless EJB's are pooled by the container. Every time a client makes a call to a Stateless EJB, the container will fetch an available instance from the pool to handle the client request. When that instance is handling the client request, that same instance will not handle any other call that is made from any given client. When that call ends, the EJB instance is returned to the pool and becomes once again available to handle client requests.
If there is no available EJB instance in the pool to handle a client request (ex: the pooled EJB instances are all busy handling other requests), the container will create a new instance, put it in the pool, and let it handle the incoming client request.
This pool is usually configured by application server, and if it becomes exhausted when a client request arrives (ex: the pool has reached its maximum size) an exception will be thrown and propagated to the remote client.
Even if there are multiple consecutive calls from the same client to the same Stateless EJB, it should never be assumed that those consecutive calls will be handled by the same EJB instance.
@Stateless public class StatelessEJB { public void doSomething(){ } }
@EJB private StatelessEJB statelessEJB; public void clientMethod() { // These consecutive calls are NOT guaranteed to // be handled by the same Stateless EJB instance statelessEJB.doSomething(); statelessEJB.doSomething(); }
As opposed to Stateless EJB's, Singleton and Stateful EJB's may be configured to handle concurrent requests from any given client.
Container managed concurrency configuration applies both to Singleton and Stateful(有状态的) EJB's - keep in mind that Stateful EJB's concurrency strategy will always consist in WRITE (exclusive) locking semantics. It allows, in conjunction with Lock and LockType primitives, to configure how concurrent access should be allowed by the container while handling EJB calls, with method level granularity:
@Singleton @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class SingletonEJB { int counter; @Lock(LockType.READ) public int readCounter() { return counter; } @Lock(LockType.WRITE) public void incrementCounter() { counter++; } }
By default, every Stateful and Singleton EJB has container managed concurrency so, in this example, the annotation@ConcurrencyManagement with its value defined as CONTAINER is redundant(默认是container,此处标记是冗余的).
The Lock annotation may have a couple of distinct values: READ and WRITE. Methods annotated with READ lock type may be accessed concurrently by any arbitrary number of clients, given the fact that there is no method configured with WRITE lock type being accessed at that moment.(读的时候确保不能有写的线程在执行)
WRITE lock type methods are exclusive, ie. if a WRITE locked method is being accessed at a given moment, no other method - READ or WRITE - may be accessed until the method execution completes. Since WRITE methods are exclusive, if a WRITE method is ready to be executed but there is(are) other method(s) executing at that time, it must wait for the them to complete in order to acquire the exclusive lock and proceed.(写的时候,不能有其他线程读或者写,必须等待当前线程完成执行)
It's important to mention that the Lock annotation may also be defined at class level, meaning that the specified access type will then be applied to all EJB methods. We may override the behavior for specific methods by applying the annotation to a given method:(锁可以加在类层级,也可以加在方法层级)
@Singleton @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) @Lock(LockType.READ) public class SingletonEJB { int counter; // This method will inherit the Lock // configuration defined at class level public int readCounter() { return counter; } // This method overrides the class level // Lock semantics by changing it to WRITE @Lock(LockType.WRITE) public void incrementCounter() { counter++; } }
We end this section mentioning the access timeout configuration. One may additionally configure the amount of time that a client request will wait for concurrent method access, before giving up. Once the timeout is reached, the container will throw an exception which will be propagated to the calling client. The timeout is configured by the @AccessTimeout annotation, which may be defined at both class and method level (the method level configuration overrides the class level configuration):
@Singleton @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) @AccessTimeout(value = 5000) public class SingletonEJB { int counter; @Lock(LockType.READ) @AccessTimeout(value = 2, unit = TimeUnit.SECONDS) public int readCounter() { return counter; } @Lock(LockType.WRITE) public void incrementCounter() { counter++; } }
The Access Timeout annotation may be configured with value and unit properties: the value property specifies the amount of time before the timeout is reached, while the unit - as the name states - specifies the unit in which the time property value is defined. The default time unit is MILLISECONDS.
In this example, the incrementCounter() method will have a timeout of 5 seconds (inherited from the class level configuration), while the method readCounter() will have a timeout of 2 seconds (overriding the class level configuration).
As the ConcurrencyManagement documentation states, bean managed concurrency is only applied to Singleton EJB's.
Bean managed concurrency is defined like the following:
@Singleton @ConcurrencyManagement(ConcurrencyManagementType.BEAN) public class SingletonEJB { public void someMethod() { } }
Bean managed concurrency means that no Java EE primitives, like Lock and LockType, may be used to manage concurrent access to a given Singleton EJB. The application is instead responsible to manage concurrent access by the means of the traditional concurrent access primitives, like synchronized, volatile, wait, notify or any other data structure or library that provides concurrent access management.(由开发者去管理并发控制,这段话自己揣摩,大致意思是Bean类型修饰的控制权限问题,我也翻译不好)