Multi-Threads: secondary cache design
Handling race conditions in secondary cache.
1 /* 2 * Copyright (c) 2013 ASP Converters pty ltd 3 * 4 * www.aspconverters.com.au. 5 * 6 * All Rights Reserved. 7 * 8 * This software is the proprietary information of 9 * ASP Converters Pty Ltd. 10 * Use is subject to license terms. 11 */ 12 import com.aspc.DBObj.*; 13 import com.aspc.DBObj.Listeners.*; 14 15 /** 16 * Use a secondary cache to fetch a "Thing". We are in a multi-machine / multi-processor / 17 * multi-user / multi-threaded environment. Records can and do change at any time from one 18 * line to the next. A few points to look out for:- 19 * 20 * 1) All work must be done with local variables so that we don"t get Null 21 * Pointer Exceptions when the cache is cleared while we are in this method. 22 * 23 * 2) Threads can take their copies of object variables which are only flushed/sync"d when 24 * synchronized is called on the object. 25 * 26 * 3) Database queries etc. can take a while to run (specially if the query returns multiple rows) 27 * a record in the result set maybe changed and a message sent/clear cache called before the 28 * result is returned. This case must be handled ( it happens a lot with bulk records) 29 * 30 * 4) Synchronizing the method synchronizes the whole Object. So for complex objects like Company 31 * or DBClass which may have many secondary caches we would be blocking a fetch of something 32 * that is in memory due to a fetch of something else that is not. 34 * 5) Having complex logic within the synchronized block which calls other objects with 35 * synchronized blocks it is easy to cause Java deadlocks. A deadlock within Java will NEVER 36 * return unlike a normal database deadlock. 37 */ 38 public class Bits extends DBObject implements DependanceListener, ReloadEventListener 39 { 40 /** 41 * Std. DBObject constructor. 42 * 43 * @param def The class of this object 44 * @param dataSource The datasource for this object 45 * @throws Exception A serious problem occurred 46 */ 47 public Bits(DBClass def, DataSource dataSource) throws Exception 48 { 49 super( def, dataSource); 50 } 51 52 /** 53 * Sample secondary cache. 54 * 55 * Step 1. 56 * Enter a synchronized block so that we see a clean version of cache of "Thing" and the cache 57 * of thing is prevented while we are within this block. 58 * 59 * Step 2. 60 * If the cache handle is null then create a new handle and set the local copy. If any 61 * clear cache events are now called it"ll clear the handle and the next call will reload the 62 * cache but this call will continue with the local handle. 63 * 64 * Step 3. 65 * If what the handle points to is null then do the Slow search and set the local handle. 66 * There is some question on whether we should sync the setting of the local handle, I 67 * believe not as another thread not getting the new value (which is flushed fairly frequently) 68 * would just result in another search. If the object"s version of the handle hasn't been 69 * cleared i.e. same as the local version it is now set. 70 * 71 * Step 4. 72 * Return the value of the local handle which will never be null. 73 */ 74 public Thing getCacheThing() throws Exception 75 { 76 Thing holder[] = null; 77 78 // OPTIONAL A: Safer if sync block is here 79 synchronized( this)// Step 1. 80 { 81 holder = cacheThing; 82 83 if( holder == null) 84 { 85 // OPTIONAL B: Faster if the sync block is here. ( must have A or B) 86 holder = new Thing[1]; 87 88 cacheThing=holder; // Step 2 89 } 90 } 91 92 if( holder[0] == null) // Step 3. 93 { 94 DBQuery q = new DBQuery( Thing.DBCLASS_NAME, getDS()); 95 96 q.addClause( /* A complex/slow search criteria */); 97 98 DBObject obj = q.findOne(); 99 // Enter a new synchronized block to prevent reordering of instructions 100 synchronized( this) 101 { 102 holder[0] = obj; 103 } 104 } 105 106 return holder[0]; // Step 4. 107 } 108 109 /** 110 * A dependent of Bits has been added. This may effect the secondary cache so we should clear it. 111 * 112 * @param addedKey The dependent added 113 * @param sourceFieldKey The field that points to this object 114 */ 115 public void eventDependantAdded( GlobalKey addedKey, GlobalKey sourceFieldKey) 116 { 117 clearCache( addedKey); 118 } 119 120 /** 121 * A that we are watching has been changed 122 * 123 * @param obj The DBObject that was reload. 124 */ 125 public void eventReload( DBObject obj ) 126 { 127 clearCache( obj.getGlobalKey()); 128 } 129 130 /** 131 * A dependent of Bits has been removed. This may effect the secondary cache. 132 * 133 * @param removedKey The DBObject was removed. 134 * @param sourceFieldKey The linked field 135 */ 136 public void eventDependantRemoved( GlobalKey removedKey, GlobalKey sourceFieldKey) 137 { 138 clearCache( removedKey); 139 } 140 141 /** 142 * We should only clear the cache if the record changed could have possibly effected the cache. 143 * This method we be called MANY times. So it is cheaper just to clear the cache if in any doubt. 144 * Eg. If you are holding the primary security for this Company and the class of the changed 145 * object is "security" don"t go selecting it here to work out if you should clear it or not. 146 * 147 * This is automatically called by eventDataLoaded() in DBObject which does a programmer check 148 * that you have call the super.clearCache( changedKey); 149 */ 150 protected void clearCache( GlobalKey changedKey) 151 { 152 super.clearCache( changedKey); 153 154 if( /* only clear if the changed object effects the secondary cache */) 155 { 156 synchronized( this)// minimize the time we spend in synchronized blocks 157 { 158 cacheThing = null; 159 } 160 } 161 } 162 163 private Thing[] cacheThing; 164 }