I’ve experience a really painful java learning curve and still a newbie.
I’m designing a low-level library class, in the future it possibly can be used in an multi-threading environment or just in a single thread. I have no idea of that.
I can let user of this class to synchronize from outside. But that will be much more inefficient than provide a thread-safety version.
This is p-code.
Class Example{
public int checkAndProcess(){
WriteLock writeLock=this.getWriteLock();
ReadLock readLock=new ReadLock(writeLock);
int a;
try{
lockManager.lock(readLock);
a=readSomething();
}finally{
lockManager.release(readLock);
}
if(a!=null){
return a;
}
try{
lockManager.lock(writeLock);
a=doSomeProcessing();
}finally{
lockManager.release(writeLock);
}
return a;
}
}
It will be much faster than synchronize from outside because readlock doesn’t block. It’s create and garbage-collected in every method call.
Problem:
Overhead. WriteLock is pretty complex, ReadLock is cheap and simple but it’s created in every method call(and possibly multiple), so still an overhead.
Should I provide a thread-safety version of every such class? Every open source library doesn’t do that. But if I don’t provide it, let user synchronize from outside, performance will be lowered.
Or is there any better way ?
Edit:
Should I split it?
Split it into a stateless processor and a store, and let user to create readLock/writeLock to lock? If I do that, the store will be totally designed for the processor, doesn’t have much meaning for other class, and the library will be quickly boomed by those things.
This is my real code. You can ignore it if you don’t like.
package lazycatTools.runtime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.jobs.Job;
import org.osgi.framework.AllServiceListener;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
public class DynamicServiceTracker {
private final HashMap<Long,Object> _serviceCache;
private final HashMap<String,Long> _keyCache;
private final MultiResourceSchedulingRule _writeLock;
private final ServiceListener _tracker;
private final BundleContext _context;
public DynamicServiceTracker(BundleContext context){
Assert.isLegal(context!=null);
_serviceCache=new HashMap<Long,Object>();
_keyCache=new HashMap<String,Long>();
HashSet<Object> lockResource=new HashSet<Object>(4);
lockResource.add(_serviceCache);
lockResource.add(_keyCache);
_writeLock=new MultiResourceSchedulingRule<DynamicServiceTracker,Object>(this,lockResource);
_context=context;
_tracker=new AllServiceListener(){
@Override
public void serviceChanged(ServiceEvent event) {
if(event.getType()==ServiceEvent.UNREGISTERING){
ServiceReference<?> ref=event.getServiceReference();
Long sid=(Long)ref.getProperty(Constants.SERVICE_ID);
String[] classes=(String[])ref.getProperty(Constants.OBJECTCLASS);
boolean ungetService=false;
try{
Job.getJobManager().beginRule(_writeLock, null);
for(String clazz : classes){
if(_keyCache.get(clazz)==sid){
_keyCache.remove(clazz);
break;
}
}
if(_serviceCache.containsKey(sid)){
_serviceCache.remove(sid);
ungetService=true;
}
}finally{
Job.getJobManager().endRule(_writeLock);
}
if(ungetService){
//The order of ungetting a serviceReference is not important
_context.ungetService(ref);
}
SharedSchedulingRule readLock=new SharedSchedulingRule(_writeLock);
try{
Job.getJobManager().beginRule(readLock, null);
if(_serviceCache.size()==0){
_context.removeServiceListener(_tracker);
}
}finally{
Job.getJobManager().endRule(readLock);
}
}
}
};
}
public Object getService(String clazz) throws Exception{
Object cachedService=null;
Long key;
SharedSchedulingRule readLock=new SharedSchedulingRule(_writeLock);
try{
Job.getJobManager().beginRule(readLock, null);
key=_keyCache.get(clazz);
if(key!=null){
cachedService=_serviceCache.get(key);
}
}finally{
Job.getJobManager().endRule(readLock);
}
if(cachedService!=null){
return cachedService;
}
ServiceReference<?> ref=_context.getServiceReference(clazz);
Long sid=(Long)ref.getProperty(Constants.SERVICE_ID);
Object newService=_context.getService(ref);
try{
Job.getJobManager().beginRule(_writeLock, null);
key=_keyCache.get(clazz);
if(key!=null){
cachedService=_serviceCache.get(key);
}else{
_keyCache.put(clazz,sid);
_serviceCache.put(sid, newService);
}
}finally{
Job.getJobManager().endRule(_writeLock);
}
if(cachedService!=null){
_context.ungetService(ref);
return cachedService;
}else{
_context.addServiceListener(_tracker);
return newService;
}
}
public <Type> Type getService(Class<Type> clazz){
Object cachedService=null;
Long key;
SharedSchedulingRule readLock=new SharedSchedulingRule(_writeLock);
try{
Job.getJobManager().beginRule(readLock, null);
key=_keyCache.get(clazz);
if(key!=null){
cachedService=_serviceCache.get(key);
}
}finally{
Job.getJobManager().endRule(readLock);
}
if(cachedService!=null){
@SuppressWarnings("unchecked")
Type castedService=(Type)cachedService;
return castedService;
}
ServiceReference<Type> ref=_context.getServiceReference(clazz);
Long sid=(Long)ref.getProperty(Constants.SERVICE_ID);
Type newService=_context.getService(ref);
try{
Job.getJobManager().beginRule(_writeLock, null);
key=_keyCache.get(clazz);
if(key!=null){
cachedService=_serviceCache.get(key);
}else{
_keyCache.put(clazz.getName(),sid);
_serviceCache.put(sid, newService);
}
}finally{
Job.getJobManager().endRule(_writeLock);
}
if(cachedService!=null){
_context.ungetService(ref);
@SuppressWarnings("unchecked")
Type castedService=(Type)cachedService;
return castedService;
}else{
_context.addServiceListener(_tracker);
return newService;
}
}
public Object[] getServices(String clazz,String filter) throws InvalidSyntaxException{
ServiceReference<?>[] refs=_context.getServiceReferences(clazz,filter);
if(refs==null){
return null;
}
Object[] services=new Object[refs.length];
int count=refs.length;
boolean[] serviceAbsence=new boolean[refs.length];
Long[] SIDs=new Long[refs.length];
for(int i=0;i<=count-1;i++){
ServiceReference<?> ref=refs[i];
SIDs[i]=(Long)ref.getProperty(Constants.SERVICE_ID);
}
boolean loop=true;
SharedSchedulingRule readLock=new SharedSchedulingRule(_writeLock);
while(loop){
try{
Job.getJobManager().beginRule(readLock, null);
for(int i=0;i<=count-1;i++){
if(_serviceCache.containsKey(SIDs[i])==false){
serviceAbsence[i]=true;
}
}
}finally{
Job.getJobManager().endRule(readLock);
}
for(int i=0;i<=count-1;i++){
if(serviceAbsence[i]==true){
services[i]=_context.getService(refs[i]);
}
}
try{
Job.getJobManager().beginRule(_writeLock, null);
boolean gotNewRequire=false;
for(int i=0;i<=count-1;i++){
if(_serviceCache.containsKey(SIDs[i])==false && services[i]==null){
serviceAbsence[i]=true;
gotNewRequire=true;
}
}
if(gotNewRequire==false){
for(int i=0;i<=count-1;i++){
Object service=services[i];
if(service!=null){
_serviceCache.put(SIDs[i], service);
}else{
services[i]=_serviceCache.get(SIDs[i]);
}
}
loop=false;
}
}finally{
Job.getJobManager().endRule(_writeLock);
}
}
_context.addServiceListener(_tracker);
return services;
}
public <Type> Collection<Type> getServices(Class<Type> clazz,String filter) throws InvalidSyntaxException{
Collection<ServiceReference<Type>> refsCollection=_context.getServiceReferences(clazz,filter);
HashMap<Integer,Type> services=new HashMap<Integer,Type>(refsCollection.size()+1,1.0f);
if(refsCollection.size()==0){
return services.values();
}
ArrayList<ServiceReference<Type>> refs=new ArrayList<ServiceReference<Type>>(refsCollection);
int count=refs.size();
boolean[] serviceAbsence=new boolean[refs.size()];
Long[] SIDs=new Long[refs.size()];
for(int i=0;i<=count-1;i++){
ServiceReference<Type> ref=refs.get(i);
SIDs[i]=(Long)ref.getProperty(Constants.SERVICE_ID);
}
boolean loop=true;
SharedSchedulingRule readLock=new SharedSchedulingRule(_writeLock);
while(loop){
try{
Job.getJobManager().beginRule(readLock, null);
for(int i=0;i<=count-1;i++){
if(_serviceCache.containsKey(SIDs[i])==false){
serviceAbsence[i]=true;
}
}
}finally{
Job.getJobManager().endRule(readLock);
}
for(int i=0;i<=count-1;i++){
if(serviceAbsence[i]==true){
services.put(i, _context.getService(refs.get(i)));
}
}
try{
Job.getJobManager().beginRule(_writeLock, null);
boolean gotNewRequire=false;
for(int i=0;i<=count-1;i++){
if(_serviceCache.containsKey(SIDs[i])==false && services.containsKey(i)==false){
serviceAbsence[i]=true;
gotNewRequire=true;
}
}
if(gotNewRequire==false){
for(int i=0;i<=count-1;i++){
Object service=services.get(i);
if(service!=null){
_serviceCache.put(SIDs[i], service);
}else{
@SuppressWarnings("unchecked")
Type cachedService=(Type)_serviceCache.get(SIDs[i]);
services.put(i,cachedService);
}
}
loop=false;
}
}finally{
Job.getJobManager().endRule(_writeLock);
}
}
_context.addServiceListener(_tracker);
return services.values();
}
}
This is MultiResourceSchedulingRule.
package lazycatTools.runtime;
import java.util.Collections;
import java.util.Set;
import java.util.HashSet;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
public class MultiResourceSchedulingRule<ParentType,ResourceType> extends ResourceBindingSchedulingRule<ParentType> implements IMultiResourceSchedulingRule<ParentType,ResourceType> {
private final Set<ResourceType> _resources;
public MultiResourceSchedulingRule(ParentType parent){
this(parent,new HashSet<ResourceType>());
}
public MultiResourceSchedulingRule(ParentType parent,Set<ResourceType> resources){
super(parent);
Assert.isLegal(resources!=null);
_resources=resources;
}
@Override
public boolean isConflicting(ISchedulingRule rule){
if(rule==this){
return true;
}
if(rule instanceof IResourceBindingSchedulingRule<?>){
final IResourceBindingSchedulingRule<?> casted=(IResourceBindingSchedulingRule<?>)rule;
if(_resources.contains(casted.getResource())){
return true;
}
}
if(rule instanceof IMultiResourceSchedulingRule<?,?>){
final IMultiResourceSchedulingRule<?,?> casted=(IMultiResourceSchedulingRule<?,?>)rule;
if(Collections.disjoint(_resources,casted.getResources())==false){
return true;
}
}
return false;
}
@Override
public boolean contains(ISchedulingRule rule){
if(rule==this){
return true;
}
if(rule instanceof IResourceBindingSchedulingRule<?>){
final IResourceBindingSchedulingRule<?> casted=(IResourceBindingSchedulingRule<?>)rule;
if(_resources.contains(casted.getResource())){
return true;
}
}
if(rule instanceof IMultiResourceSchedulingRule){
final IMultiResourceSchedulingRule<?,?> casted=(IMultiResourceSchedulingRule<?,?>)rule;
if(_resources.containsAll(casted.getResources())){
return true;
}
}
return false;
}
@Override
public Set<ResourceType> getResources() {
return Collections.<ResourceType>unmodifiableSet(_resources);
}
}
And ResourceBindingSchedulingRule.
package lazycatTools.runtime;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
public class ResourceBindingSchedulingRule<ResourceType> implements IResourceBindingSchedulingRule<ResourceType> {
private final ResourceType _resource;
public ResourceBindingSchedulingRule(ResourceType resource){
Assert.isLegal(resource!=null);
_resource=resource;
}
/* (non-Javadoc)
* @see lazycatTools.runtime.IResourceBindingSchedulingRule#getResource()
*/
@Override
public ResourceType getResource() {
return _resource;
}
@Override
public boolean contains(ISchedulingRule rule) {
return isConflicting(rule);
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
if(rule==this){
return true;
}
if(rule instanceof IResourceBindingSchedulingRule<?>){
final IResourceBindingSchedulingRule<?> casted=(IResourceBindingSchedulingRule<?>)rule;
return _resource==casted.getResource();
}
return false;
}
}
SharedSchedulingRule which is used as the readLock in the example.
package lazycatTools.runtime;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
public class SharedSchedulingRule implements ISchedulingRule {
private final ISchedulingRule _rule;
public SharedSchedulingRule(ISchedulingRule rule){
_rule=rule;
}
@Override
public boolean contains(ISchedulingRule rule) {
if(rule==this){
return true;
}
return _rule.contains(rule);
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
if(rule==this){
return true;
}
return _rule.isConflicting(rule);
}
}
The best option is to make everything immutable. Then you don’t need to worry about anything 🙂
Assuming this isn’t possible, then I would recommend not making your library thread-safe.
There are good examples in the Java standard library that follow this approach and logic – e.g.
ArrayListis not thread safe.