mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
classlib: fix ThreadLocal implementation for multi-thread case
This commit is contained in:
parent
931f0f1f4a
commit
e7c28da4e2
|
@ -38,6 +38,7 @@ public class TThread extends TObject implements TRunnable {
|
||||||
private final Object finishedLock = new Object();
|
private final Object finishedLock = new Object();
|
||||||
private boolean interruptedFlag;
|
private boolean interruptedFlag;
|
||||||
public TThreadInterruptHandler interruptHandler;
|
public TThreadInterruptHandler interruptHandler;
|
||||||
|
public Object key;
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private boolean alive = true;
|
private boolean alive = true;
|
||||||
|
@ -77,6 +78,7 @@ public class TThread extends TObject implements TRunnable {
|
||||||
try {
|
try {
|
||||||
activeCount++;
|
activeCount++;
|
||||||
setCurrentThread(TThread.this);
|
setCurrentThread(TThread.this);
|
||||||
|
key = new Object();
|
||||||
TThread.this.run();
|
TThread.this.run();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
getUncaughtExceptionHandler().uncaughtException(this, t);
|
getUncaughtExceptionHandler().uncaughtException(this, t);
|
||||||
|
@ -86,6 +88,7 @@ public class TThread extends TObject implements TRunnable {
|
||||||
}
|
}
|
||||||
alive = false;
|
alive = false;
|
||||||
activeCount--;
|
activeCount--;
|
||||||
|
key = null;
|
||||||
setCurrentThread(mainThread);
|
setCurrentThread(mainThread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.classlib.java.lang;
|
package org.teavm.classlib.java.lang;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class TThreadLocal<T> extends TObject {
|
public class TThreadLocal<T> extends TObject {
|
||||||
|
private Map<Object, Object> map;
|
||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
private T value;
|
private T value;
|
||||||
|
|
||||||
|
@ -27,21 +32,79 @@ public class TThreadLocal<T> extends TObject {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public T get() {
|
public T get() {
|
||||||
|
if (isInMainThread()) {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
value = initialValue();
|
value = initialValue();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
cleanupMap();
|
||||||
return value;
|
return value;
|
||||||
|
} else {
|
||||||
|
var key = TThread.currentThread().key;
|
||||||
|
initMap();
|
||||||
|
var value = map.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
value = initialValue();
|
||||||
|
map.put(key, value == null ? NULL : value);
|
||||||
|
} else if (value == NULL) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
cleanupMap();
|
||||||
|
return (T) value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(T value) {
|
public void set(T value) {
|
||||||
|
if (isInMainThread()) {
|
||||||
initialized = true;
|
initialized = true;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
cleanupMap();
|
||||||
|
} else {
|
||||||
|
initMap();
|
||||||
|
map.put(TThread.currentThread().key, value == null ? NULL : value);
|
||||||
|
cleanupMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove() {
|
public void remove() {
|
||||||
|
if (isInMainThread()) {
|
||||||
initialized = false;
|
initialized = false;
|
||||||
value = null;
|
value = null;
|
||||||
|
cleanupMap();
|
||||||
|
} else {
|
||||||
|
if (map != null) {
|
||||||
|
map.remove(TThread.currentThread().key);
|
||||||
|
cleanupMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMap() {
|
||||||
|
if (map == null) {
|
||||||
|
map = new WeakHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupMap() {
|
||||||
|
if (map != null && map.isEmpty()) {
|
||||||
|
map = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S> TThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
|
||||||
|
return new TThreadLocal<>() {
|
||||||
|
@Override
|
||||||
|
protected S initialValue() {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInMainThread() {
|
||||||
|
return TThread.currentThread() == TThread.getMainThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Object NULL = new Object();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.java.lang;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.teavm.classlib.PlatformDetector;
|
||||||
|
import org.teavm.interop.Async;
|
||||||
|
import org.teavm.interop.AsyncCallback;
|
||||||
|
import org.teavm.jso.browser.Window;
|
||||||
|
import org.teavm.junit.OnlyPlatform;
|
||||||
|
import org.teavm.junit.TeaVMTestRunner;
|
||||||
|
import org.teavm.junit.TestPlatform;
|
||||||
|
|
||||||
|
@RunWith(TeaVMTestRunner.class)
|
||||||
|
public class ThreadLocalTest {
|
||||||
|
private volatile int counter;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@OnlyPlatform(TestPlatform.JAVASCRIPT)
|
||||||
|
public void concurrentUpdate() throws InterruptedException {
|
||||||
|
var local = new ThreadLocal<String>();
|
||||||
|
var monitor = new Object();
|
||||||
|
var results = new String[5];
|
||||||
|
counter = results.length;
|
||||||
|
|
||||||
|
for (var n = 0; n < results.length; ++n) {
|
||||||
|
var threadIndex = n;
|
||||||
|
new Thread(() -> {
|
||||||
|
var prefix = Character.toString((char) ('a' + threadIndex));
|
||||||
|
for (var i = 0; i < 10; ++i) {
|
||||||
|
var old = local.get();
|
||||||
|
if (old == null) {
|
||||||
|
old = "";
|
||||||
|
}
|
||||||
|
local.set(old + prefix + i);
|
||||||
|
sleep();
|
||||||
|
}
|
||||||
|
synchronized (monitor) {
|
||||||
|
results[threadIndex] = local.get();
|
||||||
|
--counter;
|
||||||
|
monitor.notifyAll();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
synchronized (monitor) {
|
||||||
|
monitor.wait();
|
||||||
|
}
|
||||||
|
} while (counter > 0);
|
||||||
|
|
||||||
|
assertEquals("a0a1a2a3a4a5a6a7a8a9", results[0]);
|
||||||
|
assertEquals("b0b1b2b3b4b5b6b7b8b9", results[1]);
|
||||||
|
assertEquals("c0c1c2c3c4c5c6c7c8c9", results[2]);
|
||||||
|
assertEquals("d0d1d2d3d4d5d6d7d8d9", results[3]);
|
||||||
|
assertEquals("e0e1e2e3e4e5e6e7e8e9", results[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sleep() {
|
||||||
|
if (!PlatformDetector.isJavaScript()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sleepInJs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
private static native void sleepInJs();
|
||||||
|
|
||||||
|
private static void sleepInJs(AsyncCallback<Void> callback) {
|
||||||
|
Window.setTimeout(() -> callback.complete(null), 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user