不是线程安全的
不,从跨线程操作非线程安全List
的不是线程安全的。
Synchronized
您可以按照Bodewes 的评论handleInput
制作您的方法。当然,该方法成为潜在的瓶颈,因为一次只有一个线程可以调用同步方法。在您的情况下可能不是问题,但请注意。synchronized
线程安全的集合
另一种选择是LinkedList
用线程安全的集合替换你的。例如,CopyOnWriteArrayList
、CopyOnWriteArraySet
或ConcurrentSkipListSet
。
Callable
&Future
但我建议您不要将对象的多线程生产Entity
与收集和处理这些对象混为一谈。分配给后台线程的每个任务都应该尽可能地“管好自己的事”。让任务共享一个列表会使它们不必要地跨线程纠缠。应尽可能避免这种纠缠(共享资源)。
将您的任务从 a 更改Runnable
为 aCallable
以便返回一个值。该返回值将分别Entity
产生。
当您将每个提交Callable
到执行器服务时,您会返回一个Future
对象。收集那些物品。通过这些对象中的每一个,您都可以访问每个任务的工作结果。
等待执行器服务完成所有提交的任务。然后检查每个Future
.
通过使用Future
对象来收集后台线程产生的结果,并且只在它们全部完成后处理这些结果,我们消除了使您的results
收集线程安全的需要。原始线程将这些结果收集到一个列表中,而不是每个线程都添加到列表中。
请注意,在下面的示例代码中,我们没有在后台线程中运行的任务之间共享资源。每个任务做自己的事情,通过其特定的Future
对象报告自己的结果。这些任务不再访问共享的List
.
顺便说一句,请注意此示例代码不会像问题中的代码那样将执行程序服务保留在成员字段中。在我们这里的情况下,执行器服务应该 (1) 实例化,(2) 使用,以及 (3) 一次性关闭所有操作。您必须在不再需要时(或您的应用退出时)关闭您的执行器服务。否则它的后备线程池可能会无限期地运行,就像僵尸一样 ♂️。
示例代码
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