@@ -33,7 +33,65 @@ module Concurrent
3333 #
3434 # @see https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html Java ThreadLocal
3535 #
36- class ThreadLocalVar
36+ # @!visibility private
37+ class AbstractThreadLocalVar
38+
39+ # @!visibility private
40+ NIL_SENTINEL = Object . new
41+ private_constant :NIL_SENTINEL
42+
43+ # @!macro [attach] thread_local_var_method_initialize
44+ #
45+ # Creates a thread local variable.
46+ #
47+ # @param [Object] default the default value when otherwise unset
48+ def initialize ( default = nil )
49+ @default = default
50+ allocate_storage
51+ end
52+
53+ # @!macro [attach] thread_local_var_method_get
54+ #
55+ # Returns the value in the current thread's copy of this thread-local variable.
56+ #
57+ # @return [Object] the current value
58+ def value
59+ raise NotImplementedError
60+ end
61+
62+ # @!macro [attach] thread_local_var_method_set
63+ #
64+ # Sets the current thread's copy of this thread-local variable to the specified value.
65+ #
66+ # @param [Object] value the value to set
67+ # @return [Object] the new value
68+ def value = ( value )
69+ raise NotImplementedError
70+ end
71+
72+ # @!macro [attach] thread_local_var_method_bind
73+ #
74+ # Bind the given value to thread local storage during
75+ # execution of the given block.
76+ #
77+ # @param [Object] value the value to bind
78+ # @yield the operation to be performed with the bound variable
79+ # @return [Object] the value
80+ def bind ( value , &block )
81+ raise NotImplementedError
82+ end
83+
84+ protected
85+
86+ # @!visibility private
87+ def allocate_storage
88+ raise NotImplementedError
89+ end
90+ end
91+
92+ # @!visibility private
93+ # @!macro internal_implementation_note
94+ class RubyThreadLocalVar < AbstractThreadLocalVar
3795
3896 # Each thread has a (lazily initialized) array of thread-local variable values
3997 # Each time a new thread-local var is created, we allocate an "index" for it
@@ -57,33 +115,25 @@ class ThreadLocalVar
57115 # array, so we don't leak memory
58116
59117 # @!visibility private
60- NIL_SENTINEL = Object . new
61118 FREE = [ ]
62119 LOCK = Mutex . new
63120 ARRAYS = { } # used as a hash set
64121 @@next = 0
65- private_constant :NIL_SENTINEL , : FREE, :LOCK , :ARRAYS
122+ private_constant :FREE , :LOCK , :ARRAYS
66123
124+ # @!macro [attach] thread_local_var_method_initialize
125+ #
67126 # Creates a thread local variable.
68127 #
69128 # @param [Object] default the default value when otherwise unset
70129 def initialize ( default = nil )
71130 @default = default
72- @index = LOCK . synchronize do
73- FREE . pop || begin
74- result = @@next
75- @@next += 1
76- result
77- end
78- end
79- ObjectSpace . define_finalizer ( self , self . class . threadlocal_finalizer ( @index ) )
131+ allocate_storage
80132 end
81133
82- # Returns the value in the current thread's copy of this thread-local variable.
83- #
84- # @return [Object] the current value
134+ # @!macro thread_local_var_method_get
85135 def value
86- if array = Thread . current [ :__threadlocal_array__ ]
136+ if array = Thread . current . thread_variable_get ( :__threadlocal_array__ )
87137 value = array [ @index ]
88138 if value . nil?
89139 @default
@@ -97,30 +147,22 @@ def value
97147 end
98148 end
99149
100- # Sets the current thread's copy of this thread-local variable to the specified value.
101- #
102- # @param [Object] value the value to set
103- # @return [Object] the new value
150+ # @!macro thread_local_var_method_set
104151 def value = ( value )
105152 me = Thread . current
106153 # We could keep the thread-local arrays in a hash, keyed by Thread
107154 # But why? That would require locking
108155 # Using Ruby's built-in thread-local storage is faster
109- unless array = me [ :__threadlocal_array__ ]
110- array = me [ :__threadlocal_array__ ] = [ ]
156+ unless array = me . thread_variable_get ( :__threadlocal_array__ )
157+ array = me . thread_variable_set ( :__threadlocal_array__ , [ ] )
111158 LOCK . synchronize { ARRAYS [ array . object_id ] = array }
112159 ObjectSpace . define_finalizer ( me , self . class . thread_finalizer ( array ) )
113160 end
114161 array [ @index ] = ( value . nil? ? NIL_SENTINEL : value )
115162 value
116163 end
117164
118- # Bind the given value to thread local storage during
119- # execution of the given block.
120- #
121- # @param [Object] value the value to bind
122- # @yield the operation to be performed with the bound variable
123- # @return [Object] the value
165+ # @!macro thread_local_var_method_bind
124166 def bind ( value , &block )
125167 if block_given?
126168 old_value = self . value
@@ -135,6 +177,18 @@ def bind(value, &block)
135177
136178 protected
137179
180+ # @!visibility private
181+ def allocate_storage
182+ @index = LOCK . synchronize do
183+ FREE . pop || begin
184+ result = @@next
185+ @@next += 1
186+ result
187+ end
188+ end
189+ ObjectSpace . define_finalizer ( self , self . class . threadlocal_finalizer ( @index ) )
190+ end
191+
138192 # @!visibility private
139193 def self . threadlocal_finalizer ( index )
140194 proc do
@@ -160,43 +214,78 @@ def self.thread_finalizer(array)
160214 end
161215 end
162216 end
217+ end
163218
164- private
219+ if Concurrent . on_jruby?
165220
166- # This exists only for use in testing
167221 # @!visibility private
168- def value_for ( thread )
169- if array = thread [ :__threadlocal_array__ ]
170- value = array [ @index ]
222+ # @!macro internal_implementation_note
223+ class JavaThreadLocalVar < AbstractThreadLocalVar
224+
225+ # @!macro thread_local_var_method_get
226+ def value
227+ value = @var . get
228+
171229 if value . nil?
172230 @default
173- elsif value . equal? ( NIL_SENTINEL )
231+ elsif value == NIL_SENTINEL
174232 nil
175233 else
176234 value
177235 end
178- else
179- @default
180236 end
181- end
182237
183- private
238+ # @!macro thread_local_var_method_set
239+ def value = ( value )
240+ @var . set ( value )
241+ end
184242
185- # This exists only for use in testing
186- # @!visibility private
187- def value_for ( thread )
188- if array = thread [ :__threadlocal_array__ ]
189- value = array [ @index ]
190- if value . nil?
191- @default
192- elsif value . equal? ( NIL_SENTINEL )
193- nil
194- else
195- value
243+ # @!macro thread_local_var_method_bind
244+ def bind ( value , &block )
245+ if block_given?
246+ old_value = @var . get
247+ begin
248+ @var . set ( value )
249+ yield
250+ ensure
251+ @var . set ( old_value )
252+ end
196253 end
197- else
198- @default
254+ end
255+
256+ protected
257+
258+ # @!visibility private
259+ def allocate_storage
260+ @var = java . lang . ThreadLocal . new
199261 end
200262 end
201263 end
264+
265+ # @!visibility private
266+ # @!macro internal_implementation_note
267+ ThreadLocalVarImplementation = case
268+ when Concurrent . on_jruby?
269+ JavaThreadLocalVar
270+ else
271+ RubyThreadLocalVar
272+ end
273+ private_constant :ThreadLocalVarImplementation
274+
275+ # @!macro thread_local_var
276+ class ThreadLocalVar < ThreadLocalVarImplementation
277+
278+ # @!method initialize(default = nil)
279+ # @!macro thread_local_var_method_initialize
280+
281+ # @!method value
282+ # @!macro thread_local_var_method_get
283+
284+ # @!method value=(value)
285+ # @!macro thread_local_var_method_set
286+
287+ # @!method bind(value, &block)
288+ # @!macro thread_local_var_method_bind
289+
290+ end
202291end
0 commit comments