Java多线程内部类调用外部类



我有一个实现runnable的内部类,它发出HTTP请求,然后调用外部类的函数来处理响应。预期的行为是将所有响应附加到对象列表中。

示例代码:

public class Status {
private ExecutorService executor;
CloseableHttpClient httpClient;
List<String> results = new LinkedList<>();
public Status() {
executor = Executors.newFixedThreadPool(5);
httpClient = HttpClients.createDefault();
}

public handleInput(Entity entity) {
String result = EntityUtils.toString(httpEntity);
results.add(result); 
}
private class Action implements Runnable {
@Override
public void run() {
try {
//do some http calls
// response = httpClient.execute(httpPost);
handleInput(response.getEntity())
} catch (SomeException e) {
lastResult = false;
}
}
}}

在我的测试中,我没有遇到任何问题,但我想知道它是否是一个线程安全的操作,从多个线程的结果添加到相同的linkedlist,如果不是,什么是这种情况下的最佳设计。

不是线程安全的

不,跨线程操作非线程安全的List不是线程安全的。

Synchronized

您可以使您的handleInput方法synchronized如Bodewes所述。当然,该方法会成为潜在的瓶颈,因为一次只能有一个线程调用同步方法。也许对你来说不是问题,但请注意。

<标题>线程安全的集合h1> 一个选择是用线程安全的集合替换LinkedList。例如:CopyOnWriteArrayListCopyOnWriteArraySetConcurrentSkipListSet

Callable&Futureh1> 是我建议您不要将Entity对象的多线程生产与收集和处理这些对象混合在一起。分配给后台线程的每个任务都应该尽可能"管好自己的事"。让任务共享一个列表会不必要地将它们跨线程纠缠在一起;这种纠缠(共享资源)应该尽可能避免。

将您的任务从Runnable更改为Callable,以便返回值。返回值将是生成的每个Entity

当您将每个Callable提交给执行器服务时,您将返回一个Future对象。收集这些物品。通过这些对象,您可以访问每个任务的工作结果。

等待执行器服务完成所有提交的任务。然后检查每个Future的结果。

通过使用Future对象来收集后台线程产生的结果,并且只在完成后处理这些结果,我们已经消除了使results集合线程安全的需要。原始线程将这些结果收集到一个列表中,而不是每个线程都添加到列表中。

注意在下面的示例代码中,我们没有在后台线程中运行的任务之间共享资源。每个任务做自己的事情,通过其特定的Future对象报告自己的结果。任务不再访问共享List

顺便说一下,请注意这个示例代码并没有像Question中的代码那样将执行器服务保存在成员字段中。在我们这里的情况中,执行器服务应该在一个操作中(1)实例化、(2)利用和(3)关闭所有这些操作。你必须在不再需要的时候关闭你的executor服务(或者当你的应用退出时)。否则支持线程池可能无限期地运行,就像一个僵尸🧟‍♂️。示例代码

package work.basil.threading;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.*;
public class Status
{
record Entity( UUID uuid , Instant instant ) { }
public List < String > process ()
{
ExecutorService executorService = Executors.newFixedThreadPool( 5 );
List < WebCallTask > tasks = List.of();
try
{
tasks = List.of(
new WebCallTask( new URI( "http://www.Google.com/" ) ) ,
new WebCallTask( new URI( "http://www.DuckDuckGo.com/" ) ) ,
new WebCallTask( new URI( "http://www.Adoptium.net/" ) )
);
} catch ( URISyntaxException e )
{
e.printStackTrace();
}
List < Future < Entity > > futures = List.of();
try { futures = executorService.invokeAll( tasks ); } catch ( InterruptedException e ) { e.printStackTrace(); }
executorService.shutdown();
try { executorService.awaitTermination( 2 , TimeUnit.MINUTES ); } catch ( InterruptedException e ) { e.printStackTrace(); }
List < String > results = new ArrayList <>( tasks.size() );
for ( Future < Entity > future : futures )
{
try
{
Entity entity = future.get();
String result = this.handleInput( entity );
results.add( result );
} catch ( InterruptedException e )
{
e.printStackTrace();
} catch ( ExecutionException e )
{
e.printStackTrace();
}
}
return results;
}
public String handleInput ( Entity entity )
{
if ( Objects.isNull( entity ) ) return "Not Available.";
return entity.toString();
}
private class WebCallTask implements Callable < Entity >
{
private URI uri;
public WebCallTask ( URI uri )
{
this.uri = uri;
}
@Override
public Entity call ()
{
Entity entity = null;
try
{
// Perform some http calls.
// response = httpClient.execute(httpPost);
// Pretend to wait on network call by sleeping.
System.out.println( "Thread: " + Thread.currentThread().getId() + " is sleeping, to pretend doing network call. " + Instant.now() );
try { Thread.sleep( Duration.ofSeconds( ThreadLocalRandom.current().nextInt( 3 , 11 ) ).toMillis() ); } catch ( InterruptedException e ) { e.printStackTrace(); }
entity = new Entity( UUID.randomUUID() , Instant.now() );
System.out.println( "Thread: " + Thread.currentThread().getId() + " produced an `Entity` object. Task done. " + Instant.now() );
} catch ( Exception e )  // In your real code, you would be catching networking errors related to your networkcall.
{
e.printStackTrace();
} finally
{
return entity;  // May return `null` as a legitimate value. In real work I would use `Optional< Entity >` here to signal that `null` is a possible and legitimate value. But let's not overcomplicate this example code.
}
}
}
public static void main ( String[] args )
{
System.out.println( "Thread: " + Thread.currentThread().getId() + " is starting demo. " + Instant.now() );
Status statusApp = new Status();
List < String > output = statusApp.process();
System.out.println( "output = " + output );
System.out.println( "Thread: " + Thread.currentThread().getId() + " is ending demo. " + Instant.now() );
}
}

运行时。

Thread: 1 is starting demo. 2021-10-09T03:58:41.269177Z
Thread: 15 is sleeping, to pretend doing network call. 2021-10-09T03:58:41.286424Z
Thread: 16 is sleeping, to pretend doing network call. 2021-10-09T03:58:41.286828Z
Thread: 17 is sleeping, to pretend doing network call. 2021-10-09T03:58:41.288108Z
Thread: 16 produced an `Entity` object. Task done. 2021-10-09T03:58:44.323703Z
Thread: 15 produced an `Entity` object. Task done. 2021-10-09T03:58:46.294364Z
Thread: 17 produced an `Entity` object. Task done. 2021-10-09T03:58:46.294269Z
output = [Entity[uuid=04d73a52-79ec-4a61-becb-ce056d3aa9fa, instant=2021-10-09T03:58:46.294359Z], Entity[uuid=cc5a7266-4101-41bb-b806-8b29b77a82d0, instant=2021-10-09T03:58:44.323688Z], Entity[uuid=3cc24ad9-3ea1-4a24-98d0-c3df4bf161b6, instant=2021-10-09T03:58:46.294254Z]]
Thread: 1 is ending demo. 2021-10-09T03:58:46.321313Z

最新更新