oversigt
siden Java ‘ s tidlige dage har multithreading været et vigtigt aspekt af sproget. Runnable er kernegrænsefladen til repræsentation af multi-threaded opgaver, og Callable er en forbedret version af Runnable, der blev tilføjet i Java 1.5.
i denne artikel vil vi undersøge forskellene og anvendelserne af begge grænseflader.
Eksekveringsmekanisme
begge grænseflader er designet til at repræsentere en opgave, der kan udføres af flere tråde. Kørbare opgaver kan køres ved hjælp af Trådklassen eller Eksekutorservice, mens Callables kun kan køres ved hjælp af sidstnævnte.
returværdier
lad os se nærmere på, hvordan disse grænseflader håndterer returværdier.
3.1. Med Runnable
er Runnable-grænsefladen en funktionel grænseflade og har en enkelt run () – metode, som ikke accepterer nogen parametre og ikke returnerer nogen værdier.
dette er velegnet til situationer, hvor vi ikke leder efter et resultat af trådudførelsen, for eksempel logning af indgående begivenheder:
public interface Runnable { public void run();}
lad os forstå dette med et eksempel:
public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); }}
i dette eksempel læser tråden bare en besked fra køen og logger den i en logfil. Der returneres ingen værdi fra opgaven; opgaven kan startes ved hjælp af Eksekutorservice:
public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown();}
i dette tilfælde vil det fremtidige objekt ikke have nogen værdi.
3.2. Med konverterbar
er den konverterbare grænseflade en generisk grænseflade, der indeholder en enkelt opkaldsmetode () – som returnerer en generisk værdi V:
public interface Callable<V> { V call() throws Exception;}
lad os se på beregning af faktorialet for et tal:
public class FactorialTask implements Callable<Integer> { int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; }}
resultatet af opkaldsmetoden() returneres inden for et fremtidigt objekt:
@Testpublic void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future<Integer> future = executorService.submit(task); assertEquals(120, future.get().intValue());}
undtagelseshåndtering
lad os se, hvor egnede de er til undtagelseshåndtering.
4.1. Med Runnable
da metodesignaturen ikke har angivet “kast” – klausulen, er der ingen måde at udbrede yderligere kontrollerede undtagelser.
4.2. Med Callable
Callable ‘ s call () – metode indeholder “kaster undtagelse” – klausul, så vi let kan udbrede kontrollerede undtagelser yderligere:
public class FactorialTask implements Callable<Integer> { // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... }}
i tilfælde af at køre en konverterbar ved hjælp af en Eksekutortjeneste indsamles undtagelserne i det fremtidige objekt, som kan kontrolleres ved at ringe til fremtiden.få () metode. Dette vil kaste en Henrettelsesundtagelse – som ombryder den oprindelige undtagelse:
@Test(expected = ExecutionException.class)public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> future = executorService.submit(task); Integer result = future.get().intValue();}
i ovenstående test er Udførelsenundtagelse bliver kastet, da vi sender et ugyldigt nummer. Vi kan kalde getCause () – metoden på dette undtagelsesobjekt for at få den originale kontrollerede undtagelse.
hvis vi ikke foretager opkaldet til get () – metoden for fremtidig klasse – rapporteres undtagelsen kastet af call () – metoden ikke tilbage, og opgaven markeres stadig som afsluttet:
@Testpublic void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> future = executorService.submit(task); assertEquals(false, future.isDone());}
ovenstående test vil bestå med succes, selvom vi har kastet en undtagelse for de negative værdier af parameteren til FactorialCallableTask.
konklusion
i denne artikel har vi undersøgt forskellene mellem de kørbare og konverterbare grænseflader.
som altid er den komplette kode til denne artikel tilgængelig på GitHub.
kom i gang med Spring 5 og Spring Boot 2, gennem Learn Spring course:
>> tjek kurset