@@ -171,19 +171,42 @@ public <T> Coder<T> getCoder(TypeDescriptor<T> typeDescriptor) {
171
171
172
172
private final List <String > nonDeterministicReasons ;
173
173
174
- private final DatumWriter <T > writer ;
175
- private final DatumReader <T > reader ;
176
- private final EncoderFactory encoderFactory = new EncoderFactory ();
177
- private final DecoderFactory decoderFactory = new DecoderFactory ();
174
+ // Factories allocated by .get() are thread-safe and immutable.
175
+ private static final EncoderFactory ENCODER_FACTORY = EncoderFactory .get ();
176
+ private static final DecoderFactory DECODER_FACTORY = DecoderFactory .get ();
177
+ // Cache the old encoder/decoder and let the factories reuse them when possible. To be threadsafe,
178
+ // these are ThreadLocal. This code does not need to be re-entrant as AvroCoder does not use
179
+ // an inner coder.
180
+ private final ThreadLocal <BinaryDecoder > decoder ;
181
+ private final ThreadLocal <BinaryEncoder > encoder ;
182
+ private final ThreadLocal <DatumWriter <T >> writer ;
183
+ private final ThreadLocal <DatumReader <T >> reader ;
178
184
179
185
protected AvroCoder (Class <T > type , Schema schema ) {
180
186
this .type = type ;
181
187
this .schema = schema ;
182
188
183
- nonDeterministicReasons = new AvroDeterminismChecker ()
184
- .check (TypeDescriptor .of (type ), schema );
185
- this .reader = createDatumReader ();
186
- this .writer = createDatumWriter ();
189
+ nonDeterministicReasons = new AvroDeterminismChecker ().check (TypeDescriptor .of (type ), schema );
190
+
191
+ // Decoder and Encoder start off null for each thread. They are allocated and potentially
192
+ // reused inside encode/decode.
193
+ this .decoder = new ThreadLocal <>();
194
+ this .encoder = new ThreadLocal <>();
195
+
196
+ // Reader and writer are allocated once per thread and are "final" for thread-local Coder
197
+ // instance.
198
+ this .reader = new ThreadLocal <DatumReader <T >>() {
199
+ @ Override
200
+ public DatumReader <T > initialValue () {
201
+ return createDatumReader ();
202
+ }
203
+ };
204
+ this .writer = new ThreadLocal <DatumWriter <T >>() {
205
+ @ Override
206
+ public DatumWriter <T > initialValue () {
207
+ return createDatumWriter ();
208
+ }
209
+ };
187
210
}
188
211
189
212
/**
@@ -233,17 +256,22 @@ private Object writeReplace() {
233
256
}
234
257
235
258
@ Override
236
- public void encode (T value , OutputStream outStream , Context context )
237
- throws IOException {
238
- BinaryEncoder encoder = encoderFactory .directBinaryEncoder (outStream , null );
239
- writer .write (value , encoder );
240
- encoder .flush ();
259
+ public void encode (T value , OutputStream outStream , Context context ) throws IOException {
260
+ // Get a BinaryEncoder instance from the ThreadLocal cache and attempt to reuse it.
261
+ BinaryEncoder encoderInstance = ENCODER_FACTORY .directBinaryEncoder (outStream , encoder .get ());
262
+ // Save the potentially-new instance for reuse later.
263
+ encoder .set (encoderInstance );
264
+ writer .get ().write (value , encoderInstance );
265
+ // Direct binary encoder does not buffer any data and need not be flushed.
241
266
}
242
267
243
268
@ Override
244
269
public T decode (InputStream inStream , Context context ) throws IOException {
245
- BinaryDecoder decoder = decoderFactory .directBinaryDecoder (inStream , null );
246
- return reader .read (null , decoder );
270
+ // Get a BinaryDecoder instance from the ThreadLocal cache and attempt to reuse it.
271
+ BinaryDecoder decoderInstance = DECODER_FACTORY .directBinaryDecoder (inStream , decoder .get ());
272
+ // Save the potentially-new instance for later.
273
+ decoder .set (decoderInstance );
274
+ return reader .get ().read (null , decoderInstance );
247
275
}
248
276
249
277
@ Override
@@ -272,12 +300,12 @@ public void verifyDeterministic() throws NonDeterministicException {
272
300
}
273
301
274
302
/**
275
- * Returns a new DatumReader that can be used to read from
276
- * an Avro file directly. Assumes the schema used to read is
277
- * the same as the schema that was used when writing.
303
+ * Returns a new {@link DatumReader} that can be used to read from an Avro file directly. Assumes
304
+ * the schema used to read is the same as the schema that was used when writing.
278
305
*
279
306
* @deprecated For {@code AvroCoder} internal use only.
280
307
*/
308
+ // TODO: once we can remove this deprecated function, inline in constructor.
281
309
@ Deprecated
282
310
public DatumReader <T > createDatumReader () {
283
311
if (type .equals (GenericRecord .class )) {
@@ -288,11 +316,11 @@ public DatumReader<T> createDatumReader() {
288
316
}
289
317
290
318
/**
291
- * Returns a new DatumWriter that can be used to write to
292
- * an Avro file directly.
319
+ * Returns a new {@link DatumWriter} that can be used to write to an Avro file directly.
293
320
*
294
321
* @deprecated For {@code AvroCoder} internal use only.
295
322
*/
323
+ // TODO: once we can remove this deprecated function, inline in constructor.
296
324
@ Deprecated
297
325
public DatumWriter <T > createDatumWriter () {
298
326
if (type .equals (GenericRecord .class )) {
0 commit comments