我必须创建一个需要使用线程的应用程序,但它们不能正常工作
Posted
技术标签:
【中文标题】我必须创建一个需要使用线程的应用程序,但它们不能正常工作【英文标题】:I have to create an application where I need to use threads, but they don't work as they should 【发布时间】:2021-12-25 11:49:45 【问题描述】:我有这段代码,其中每个人都由一个用随机票数据实例化的特殊线程表示,每 5 秒一个 FestivalStatisticsThread 唤醒并从门读取新数据并生成统计信息(有多少人进入并使用什么类型的票)。
public class Main
public static void main(String[] args) throws InterruptedException
FestivalGate gate = new FestivalGate();
FestivalAttendeeThread festivalAttendee;
FestivalStatisticsThread statsThread = new FestivalStatisticsThread(gate);
TicketType ticketType;
for (int i = 1; i < 85_000; i++)
ticketType = TicketType.randomTicket();
festivalAttendee = new FestivalAttendeeThread(ticketType, gate);
festivalAttendee.start();
festivalAttendee.join();
statsThread.start();
System.out.println(gate.getValidatedTickets().size());
问题是我没有找到一种方法来每 5 秒唤醒一次该线程并生成统计信息,我能做到的唯一方法是在最后只生成一次统计信息 第二个问题是我应该在启动festivalAttendee 线程后使用join,否则我存储票证类型的列表不会存储所有票证类型,尽管是同步的。
我还将在这里留下执行我的代码内部工作的代码:
public class FestivalGate
private List<TicketType> validatedTickets = Collections.synchronizedList(new ArrayList<>());
public List<TicketType> getValidatedTickets()
return validatedTickets;
public class FestivalAttendeeThread extends Thread
TicketType ticketType;
FestivalGate festivalGate;
public FestivalAttendeeThread(TicketType ticketType, FestivalGate festivalGate)
this.ticketType = ticketType;
this.festivalGate = festivalGate;
@Override
public synchronized void run()
this.festivalGate.getValidatedTickets().add(ticketType);
public class FestivalStatisticsThread extends Thread
private int NR_OF_FULL;
private int NR_OF_FULL_VIP;
private int NR_OF_FREE_PASS;
private int NR_OF_ONE_DAY;
private int NR_OF_ONE_DAY_VIP;
private int TOTAL_NUMBER_OF_PEOPLE;
private FestivalGate gate;
public FestivalStatisticsThread(FestivalGate gate)
this.gate = gate;
@Override
public void run()
calculateNumberOfEachTicketType();
calculateNumberOfPeople();
showStats();
try
Thread.sleep(5000);
catch (InterruptedException e)
e.printStackTrace();
private void calculateNumberOfEachTicketType()
synchronized (this.gate.getValidatedTickets())
for (TicketType ticketType : this.gate.getValidatedTickets())
if (ticketType.equals(TicketType.FULL))
NR_OF_FULL += 1;
else if (ticketType.equals(TicketType.FULL_VIP))
NR_OF_FULL_VIP += 1;
else if (ticketType.equals(TicketType.FREE_PASS))
NR_OF_FREE_PASS += 1;
else if (ticketType.equals(TicketType.ONE_DAY))
NR_OF_ONE_DAY += 1;
else if (ticketType.equals(TicketType.ONE_DAY_VIP))
NR_OF_ONE_DAY_VIP += 1;
private void calculateNumberOfPeople()
TOTAL_NUMBER_OF_PEOPLE +=
NR_OF_FULL +
NR_OF_FULL_VIP +
NR_OF_FREE_PASS +
NR_OF_ONE_DAY +
NR_OF_ONE_DAY_VIP
;
public void showStats()
System.out.println(
TOTAL_NUMBER_OF_PEOPLE + " people have entered " + "\n" +
NR_OF_FULL + " have full tickets " + "\n" +
NR_OF_FREE_PASS + " have free passes " + "\n" +
NR_OF_FULL_VIP + " have full vip passes " + "\n" +
NR_OF_ONE_DAY + " have one day passes " + "\n" +
NR_OF_ONE_DAY_VIP + " have one day vip passes"
);
System.out.println("-----------------------------------------------------------");
System.out.println("-----------------------------------------------------------");
【问题讨论】:
在启动后加入线程将停止执行for循环,直到该线程完成。 @Marvin 如果我不使用 join 不是所有类型的票都将存储在数组中 【参考方案1】:引用Oracle tutorial:
t.join()
导致当前线程暂停执行,直到 t 的线程终止
因此,您实际上并没有真正使用线程。每次通过for
循环时,都会启动一个线程,然后等待它完成。这样做没有意义,您可以放弃线程并在当前线程中按顺序执行任务。
在现代 Java 中,我们很少需要直接寻址 Thread
类。相反,我们将Runnable
/Callable
任务提交给ExecutorService
。
要安排每 5 秒重复一次计算,请使用 ScheduledExecutorService
。
ScheduledExecutorService ses = Executors. newSingleThreadScheduledExecutor() ;
…
ses.scheduleAtFixedRate( myStatsCalcRunnable , 1 , 5 , TimeUnit.SECONDS ) ;
注意:请务必最终关闭您的执行程序服务。否则它们的后备线程池可能会无限期地继续运行,就像僵尸一样?♂️。
改变这个:
public class FestivalStatisticsThread extends Thread …
...用run
方法进入:
public class FestivalStatistics …
... 使用 recalculate
方法。从Runnable
任务中调用recalculate
方法,该任务已提交到您计划的执行器服务。
或者也许该功能应该只是Gate
类的一部分。 (我不确定。我得再考虑一下。)
85,000 个并发线程可能对普通硬件造成太大负担。相反,使用有限数量的线程启动执行器服务。然后将您的 85,000 个任务提交为 Runnable
/Callable
对象。执行器服务负责提供要在有限数量的线程上执行的任务。
将来,如果Project Loom 成功,您将能够一次使用 85,000 个(甚至数百万个)虚拟线程(也称为“纤程”)。要了解更多信息,请参阅 Ron Pressler 或 Loom 团队的其他成员最近的演讲。现在可以使用基于早期访问 Java 18 的实验性构建。 Loom 团队寻求反馈。
所有这些主题都已在 Stack Overflow 上得到解决。搜索以了解更多信息。我自己和其他人一样,用代码示例编写了多个答案。
顺便说一句,我会让FestivalGate
更加封装。其他类不应访问,甚至不知道其内部使用的同步List
。添加收集和报告工单的方法。
这是我编写的一些快速代码,用于演示上述主题。
使用风险自负;我没有进行任何认真的测试,也没有进行过深思熟虑的代码审查。
也许我过分简化了您的域问题。您当然可以根据需要扩展或重新排列。但希望这清楚地表明了如何使用执行器服务。
我们有一个基本的Ticket
类来代表每张票。我们在这里使用record 来定义该类。我们嵌套了工单类型的枚举。
package work.basil.festival;
import java.util.UUID;
public record Ticket( UUID id , Type type )
enum Type
FULL_VIP, FREE_PASS, ONE_DAY, ONE_DAY_VIP
我们有一个Gate
类来保存我们的域逻辑。我们嵌套了一个 Statistics
类来保存我们报告的到目前为止所拍摄票证的摘要。
package work.basil.festival;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class Gate
final private Set < Ticket > ticketsTaken;
public Gate ( )
this.ticketsTaken = new HashSet <>();
synchronized public boolean takeTicket ( final Ticket ticket )
// Simulate hard work by sleeping some random amount of time.
int millis = ThreadLocalRandom.current().nextInt( 2 );
try Thread.sleep( millis ); catch ( InterruptedException e ) e.printStackTrace();
return this.ticketsTaken.add( ticket );
synchronized public List < Ticket > ticketsTaken ( )
return List.copyOf( this.ticketsTaken ); // Returns unmodifiable list of the `Ticket` objects contained in our private member set.
record Statistics( Instant when , int countTicketsTaken , Map < Ticket.Type, Integer > countOfTicketsTakenByType )
synchronized public Statistics statistics ( )
int count = this.countTicketsTaken();
Map < Ticket.Type, Integer > map = this.reportCountOfTicketsTakenByType();
if ( count != map.values().stream().mapToInt( Integer :: intValue ).sum() ) throw new IllegalStateException( "Counts do not match in Gate.Statistics. Error # 898e905f-9432-4195-a3e0-118bede2872d." );
return new Statistics( Instant.now() , count , map );
private int countTicketsTaken ( )
return this.ticketsTaken.size();
private Map < Ticket.Type, Integer > reportCountOfTicketsTakenByType ( )
// We use `AtomicInteger` here not for thread-safety, but for the convenience of its `incrementAndGet` method.
Map < Ticket.Type, AtomicInteger > map = new EnumMap < Ticket.Type, AtomicInteger >( Ticket.Type.class );
Arrays.stream( Ticket.Type.values() ).forEach( type -> map.put( type , new AtomicInteger( 0 ) ) ); // Initialize the map, so each ticket-type has an atomic integer set to zero.
this.ticketsTaken.stream().forEach( ticket -> map.get( ticket.type() ).incrementAndGet() );
// Convert the AtomicInteger values to simply `Integer` values.
Map < Ticket.Type, Integer > result = map.entrySet().stream().collect( Collectors.toMap( Map.Entry :: getKey , atomicInteger -> atomicInteger.getValue().intValue() ) );
return Map.copyOf( result ); // Return an unmodifiable map, as a good practice.
我们有一个App
课程来进行演示。
package work.basil.festival;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.IntStream;
/**
* Hello world!
*/
public class App
public static void main ( String[] args )
System.out.println( "Hello World!" );
App app = new App();
app.demo();
private void demo ( )
final List < Ticket > ticketsToTake = this.generateTicketsForFestival();
Gate gate = new Gate();
// Report every five seconds on the progress of our gate taking tickets.
ScheduledExecutorService reportingExecutorService = Executors.newSingleThreadScheduledExecutor();
reportingExecutorService.scheduleAtFixedRate( ( ) -> System.out.println( gate.statistics() ) , 0 , 5 , TimeUnit.SECONDS );
ExecutorService ticketTakingExecutorService = Executors.newFixedThreadPool( 7 );
for ( Ticket ticket : ticketsToTake )
ticketTakingExecutorService.submit( ( ) -> gate.takeTicket( ticket ) );
ticketTakingExecutorService.shutdown();
try ticketTakingExecutorService.awaitTermination( 10 , TimeUnit.MINUTES ); catch ( InterruptedException e ) e.printStackTrace();
reportingExecutorService.shutdown();
try reportingExecutorService.awaitTermination( 10 , TimeUnit.MINUTES ); catch ( InterruptedException e ) e.printStackTrace();
System.out.println( "« FIN » " + gate.statistics() );
private List < Ticket > generateTicketsForFestival ( )
List < Ticket > tickets = new ArrayList <>();
for ( int i = 0 ; i < 85_000 ; i++ )
tickets.add(
new Ticket(
UUID.randomUUID() , // Randomly generate a UUID, to identify uniquely each ticket.
Ticket.Type.values()[ ThreadLocalRandom.current().nextInt( Ticket.Type.values().length ) ] // Randomly pick one of the ticket types.
)
);
return List.copyOf( tickets );
在 8 核 M1 MacBook Pro 上运行时。
Hello World!
Statistics[when=2021-11-14T02:28:52.746596Z, countTicketsTaken=0, countOfTicketsTakenByType=FREE_PASS=0, ONE_DAY=0, ONE_DAY_VIP=0, FULL_VIP=0]
Statistics[when=2021-11-14T02:28:57.800514Z, countTicketsTaken=7517, countOfTicketsTakenByType=FREE_PASS=1862, ONE_DAY=1953, ONE_DAY_VIP=1889, FULL_VIP=1813]
Statistics[when=2021-11-14T02:29:02.804886Z, countTicketsTaken=15128, countOfTicketsTakenByType=FREE_PASS=3791, ONE_DAY=3788, ONE_DAY_VIP=3775, FULL_VIP=3774]
Statistics[when=2021-11-14T02:29:07.746712Z, countTicketsTaken=22819, countOfTicketsTakenByType=FREE_PASS=5764, ONE_DAY=5653, ONE_DAY_VIP=5703, FULL_VIP=5699]
Statistics[when=2021-11-14T02:29:12.769943Z, countTicketsTaken=30577, countOfTicketsTakenByType=FREE_PASS=7687, ONE_DAY=7631, ONE_DAY_VIP=7641, FULL_VIP=7618]
Statistics[when=2021-11-14T02:29:17.803627Z, countTicketsTaken=38146, countOfTicketsTakenByType=FREE_PASS=9553, ONE_DAY=9552, ONE_DAY_VIP=9554, FULL_VIP=9487]
Statistics[when=2021-11-14T02:29:22.785355Z, countTicketsTaken=45896, countOfTicketsTakenByType=FREE_PASS=11455, ONE_DAY=11497, ONE_DAY_VIP=11499, FULL_VIP=11445]
Statistics[when=2021-11-14T02:29:27.768809Z, countTicketsTaken=53563, countOfTicketsTakenByType=FREE_PASS=13448, ONE_DAY=13393, ONE_DAY_VIP=13386, FULL_VIP=13336]
Statistics[when=2021-11-14T02:29:32.739398Z, countTicketsTaken=61189, countOfTicketsTakenByType=FREE_PASS=15358, ONE_DAY=15291, ONE_DAY_VIP=15310, FULL_VIP=15230]
Statistics[when=2021-11-14T02:29:37.751764Z, countTicketsTaken=68758, countOfTicketsTakenByType=FREE_PASS=17214, ONE_DAY=17136, ONE_DAY_VIP=17226, FULL_VIP=17182]
Statistics[when=2021-11-14T02:29:42.759303Z, countTicketsTaken=76446, countOfTicketsTakenByType=FREE_PASS=19136, ONE_DAY=19057, ONE_DAY_VIP=19171, FULL_VIP=19082]
Statistics[when=2021-11-14T02:29:47.768858Z, countTicketsTaken=84030, countOfTicketsTakenByType=FREE_PASS=21086, ONE_DAY=20930, ONE_DAY_VIP=21062, FULL_VIP=20952]
« FIN » Statistics[when=2021-11-14T02:29:48.406351Z, countTicketsTaken=85000, countOfTicketsTakenByType=FREE_PASS=21321, ONE_DAY=21174, ONE_DAY_VIP=21305, FULL_VIP=21200]
【讨论】:
以上是关于我必须创建一个需要使用线程的应用程序,但它们不能正常工作的主要内容,如果未能解决你的问题,请参考以下文章