
a szálas fejtörők megoldása néha nagyon meglepő. ezt bizonyítja a keddi is: kimenete érdekes módon mindig false. egy kis gondolkodás után egyértelmű, hogy ez csak akkor fordulhat elő, ha az interrupted() metódus két egymást követő hívása különböző értékkel tér vissza. ez valóban így van. a dokumentáció el is árulja, hogy az interrupted() metódus törli az interrupted flaget.
az interrupted flag a szálak egy boolean tulajdonsága. azt mutatja meg, hogy az adott szál futását megszakították-e. kezdetben a flag értéke false, viszont ha meghívjuk a szálon az interrupt() metódust, akkor true-ra áll. hogy innen mi történik, az már attól függ, hogy mivel foglalkozott éppen a megszakított szál.
ha a thread egy blokkoló hívásban volt (mit a wait(), a Thread.sleep() és a Thead.join() metódusok) akkor az interrupted flag azonnal törlődik is és kapunk egy InterruptedExceptiont. tehát amikor az InterruptedExceptiont elkapjuk, akkor az interrupted() metódus már false értéket ad vissza. ez logikus is, hiszen mielőtt a blokkoló metódus eldobta a kivételt, azelőtt ellenőrizte, hogy mi a helyzet az interrupted flaggel. ehhez pedig az interrupted() metódust használta, amivel törölte is a flaget.
emiatt a viselkedés miatt egyébként nem szabad az InterruptedExceptiont egyszerűen elkapni és nem csinálni semmit. ennél még az is jobb megoldás, ha tovább dobjuk. ha ugyanis elkapjuk a kivételt és nem foglalkozunk vele, akkor elvész az az információ, hogy a szálat megszakították. egy megoldás, ha a catch ágban az interrupt() metódussal újra beállítjuk a flaget. de van még több megoldás is. erről brian goetz írását érdemes elolvasni.
a másik eset az, amikor a szál nem volt blokkoló hívásban. ilyen a fenti példa is. ilyenkor csak egyszerűen beállítódik az interrupted flag. itt csak a programozón múlik, hogy mit kezd vele. udvariasságból figyelembe veheti és megszakíthatja az éppen futó műveletet, de figyelmen kívül is hagyhatja. a nem megszakítható taszkoknak is szán egy bekezdést goetz, érdemes elolvasni.
a tanulság ebből is az, hogy egy ilyen egyszerű fejtörőből nagyon sokat lehet tanulni, sok szenvedést lehet megelőzni.
mit ír ki az alábbi program, ha egyáltalán lefordul?
class Interruptee implements Runnable {
public void run() {
while (!Thread.interrupted()) {
}
System.out.println(Thread.interrupted());
}
}
class InterrupteeTest {
public static void main(String... args) {
Thread t = new Thread(new Interruptee());
t.start();
t.interrupt();
}
}
majdnem hat éve kellett írnom az egyetemen egy programot mesterséges intelligenciából. már nem emlékszem, hogy pontosan mi volt a feladat, de az biztos, hogy egy darabig elvoltam vele. ha akkor találkoztam volna a Chocoval, talán pár perc alatt letudhattam volna a beadandót.
a Choco egy Javában implementált constraint solver. a constraint programming röviden arról szól, hogy a megoldandó problémát leírjuk egy megfelelő formában, majd odaadjuk egy szoftvernek, ami kodobja nekünk a megoldást. tehát mi mondjuk meg, hogy milyen lépések vezetnek a megoldáshoz: a szoftver maga elég okos ahhoz, hogy ezt kitalálja. azaz deklaratívan programozunk. a Choco egy olyan szoftver, ami képes erre.
a problémát persze nem természetes nyelven írjuk le. először is azonosítanunk kell a fő változókat. aztán meg kell mondanunk, hogy ezekre a változókra milyen megszorítások vonatkoznak. ezzel a két lépéssel meg is alkottuk a modellünk. ezt már megetethetjük a solverrel, ami előállítja nekünk a megoldást (ha van).
vegyük például az n királynő problémát. rövid átgondolás után kiderül, hogy itt n darab változóra lesz szükség. az i-edik változóból tudjuk, hogy az i-edik sor királynője melyik oszlopban áll. hogy a modell teljes legyen, meg kell mondanunk, hogy milyen egy helyes megoldás szerkezete, azaz milyen kapcsolat van egy helyes megoldásban a változók között (nem lehet több királynő egy ozlopban és átlóban).
Choco-ra átfogalmazva ez így néz ki:
Model model = new CPModel();
// creating n variables with prefix Q, lower bound 1, upper bound n
IntegerVariable[] queens = Choco.makeIntVarArray("Q", n, 1, n);
// defines constraints
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int k = j - i;
// no two queens in the same column
model.addConstraint(Choco.neq(queens[i], queens[j]));
// no two queens in the same diagonal
model.addConstraint(Choco.neq(queens[i], Choco.plus(queens[j], k)));
model.addConstraint(Choco.neq(queens[i], Choco.minus(queens[j], k)));
}
}
// solves the problem
Solver solver = new CPSolver();
solver.read(model);
solver.solve();
// prints the solution
for (int i = 0; i < n; i++) {
System.out.println(solver.getVar(queens[i]).getVal());
}
az elején megalkotjuk a modellt, majd a solve() hívással megoldjuk a problémát. nem kell törődnünk azzal, hogy a megoldás hogy áll elő, a Choco nagyon okos.
a Choco-t letölthetitek innen. ugyanitt találtok dokumentációt és példákat is. szerencsére van egy elég beszédes leírás a működéséről, példákkal bőven ellátva.
valószínűleg a közeljövőben nem lesz szükségem ilyesmire, de jó volt vele szórakozni egy kicsit.
a jsr-166-ból rengeteg dolog bekerül a jdk7-be, viszont pár dolog hiányzoni fog. az egyik ilyen a ParallelArray.
a ParallelArraynek két fontos előnye van, amivel sem a tömb, sem a kollekciók nem rendelkeznek. az egyik az, hogy funkcionális nyelvekhez hasonlóan manipulálhatjuk az elemeit (különböző mappingeket, aggregátumokat stb.) készíthetünk. a másik nagy előnye az, hogy a műveleteket képes párhuzamosítani. ehhez a ForkJoinPool funcionalitását használja fel (ami a JDK7 egy nagy újdonsága lesz). azaz ha például több processzoros gépen használjuk, akkor anélkül, hogy bármit tennénk, a feladatokat képes elosztani a processzorok között. ez pedig nem kis dolog.
a metódusait négy csoportba lehet besorolni:
import jsr166y.ForkJoinPool;
import extra166y.ParallelArray;
import extra166y.ParallelArrayWithFilter;
import extra166y.Ops;
class UpperCaser implements Ops.Op<String, String> {
public String op(String s) {
return s.toUpperCase();
}
}
class LengthFilter implements Ops.Predicate<String> {
public boolean op(String s) {
return s.length() < 3;
}
}
class ParallelArrayTest {
public static void main(String... args) {
ForkJoinPool pool = new ForkJoinPool();
ParallelArray<String> dummy = ParallelArray.createFromCopy(args, pool);
System.out.println(dummy.withFilter(new LengthFilter())
.replaceWithMapping(new UpperCaser()));
}
}
ez a példa egy sztringtömb minden 3 karakternél rövidebb elemét nagybetűsre cseréli.
az API szerencsére nagyon egyszerű, viszont sokmindent kapunk tőle. a probléma valószínűleg az volt vele, hogy szinte minden metódus egy interfész implementációt vár paraméterként, ez pedig nagyon beszédes kódhoz vezet. ehhez a viszonylag egyszerű példához két osztályt kellett készítenem. nyilvánvaló, hogy ezt senki nem szereti. a megoldást a lambda kifejezések jelentenék, amikről viszont csak határidő után derült ki, hogy mégis bekerülnek a nyelvbe.
persze azért érdemes fdoglalkozni az API-val, sokdszor hasznos lehet, főleg a párhuzamosítás miatt. letölteni doug lea oldaláról tudjárok.
a java elődjében, az oak-ban volt rengeteg furcsaság. szerencsére ezek nagy része eltűnt, viszont pár dolog megmaradt. ha például megkérdezném, hogy mit ír ki az alábbi kód, akkor a programozók legnagyobb része azonnal rávágná, hogy semmit:
class NothingSpecial extends Thread {
public void run() {
try {
while(true) {
}
}catch(NullPointerException ex) {
System.out.println("?");
}
}
}
a while magja üres, így nem dobódhat benne NullPointerException. vagy mégis? sajnos előfordulhat olyan eset, amikor ez a kód az "?" sztringet írja ki.
az oak nyelvben létezett az aszinkron kivétel fogalma, és ez megmaradt a javában is (a nyelvi specifikációban szántak is rá egy rövid bekezdést). egy kivétel attól aszinkron, hogy nem az a szál hozza létre, amiben kezelni kell. előfordulhat JVM InternalError esetén, vagy a Thread.stop() metódus meghívásakor. a stop() metódusnak átadhatunk egy Throwable példányt és ez a kivétel fog eldobódni a szában. nem véletlen, hogy ez a metódus deprecated: ez a viselkedés nagyon veszélyes is lehet. a fenti példában is olyan helyen fordul elő kivétel, ahol normális körülmények között nem számítanánk rá.
a kivételt előállító kód így néz ki:
class SomethingSpecial {
public static void main(String... args) throws Exception{
NothingSpecial thread = new NothingSpecial();
thread.start();
Thread.sleep(200);
thread.stop(new NullPointerException());
Thread.sleep(200);
}
}
Tanulság: a stop() metódus nem véletlenül deprecated.
a JDK7 újdonságai között hosszú huzavona után végül ott lesznek a lambda kifejezések. azt még senki nem tudja, milyen formában és milyen mélységben, de bekerülnek a nyelvbe. a vitákról és a különböző specifikációkról rengeteg anyagot találhattok, mivel az utóbbi időben minden nagy javás arc ezzel a témával foglalkozott. kezdésnek talán neal gafter blogján érdemes végigfutni. valószínűleg amit ő leír nincs túl távol attól, ami végül a nyelvben megjelenik.
addig is amíg ez az újítás nem jelenik meg, kereshetünk olyan megoldásokat, amik nagyon emlékeztetnek a lambda kifejezésekre. itt van például ez:
thread = java.lang.Thread(
function() {
print(java.lang.Thread.currentThread().getName());
}
);
thread.start();
hogy mi is ez? pontosan az, amit a legtöbb java programozó lambda kifejezés néven a nyelvben látni szeretne. itt egy függvényt adunk át a Thread konstruktorának, ami tudja, hogy ezt hívja meg a run() metódus helyett (ez egyébként nem teljesen így van, de szép gondolat).
sajnos azonban erre még várnunk kell: a fenti kód ugyanis egy javascript részlet. a tegnapelőtt postban írtam a rhinoról, a java scripting api egy javascript script engine implementációjáról. akkor elmondtam, hogy a rhinoban használhatunk java objektumokat, kihagytam viszont egy érdekes dolgot. abban az esetben, ha egy olyan interfészt kellene implementálnunk, amiben csak egy metódus van, a rhino megengedi, hogy csak a függvényimplementációt adjuk át. ebből ő majd előállítja a megfelelő interfész implementációt, de arról nekünk nem kell tudnunk. (a single abstract method (SAM) osztályok fogalma egyébként a java lambda kifejezés implementációja kapcsán is felmerült).
a fenti példában tehát előáll egy olyan Runnable implementáció. ha mégis magunknak akarjuk implementálni az interfészt, akkor azt így tehetjük meg:
var o = { run: function() {
print(java.lang.Thread.currentThread().getName());
}
};
var r = new java.lang.Runnable(o);
még több érdekességet találhattok a rhino és a java kapcsolatáról a rhino dokumentációban.
a java se 6 óta tudunk javascriptet futtatni java kódban. ez azért előnyös, mert használhatunk java objektumokat miközben megmarad a javascript egyszerűsége. ezen a keverék példakódon ez jól látszik:
importPackage(javax.swing);
function drawWindow(text) {
var frame = new JFrame();
var label = new JLabel(text.toString());
frame.add(label);
frame.pack();
frame.setVisible(true);
}
a szkriptek értelmezéséhez szükséges eszközök a javax.script csomagba kerültek. három fontos osztállyal kell itt foglalkoznunk:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByMimeType("text/javascript");
engine.eval(new FileReader("test.js"));
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("drawWindow", "alma");
(a fenti javascript kód a test.js fájlban van). a JDK-ban egyébként alapból benne van a mozzilla rhino javascript implementáció. mivel azonban a ScriptEngineManager a service loader mechanizmust használja az implementációk megtalálására, ezért nagyon könnyű új megvalósításokat beilleszteni.
a témával kapcsolatban találhattok itt egy jó prezentációt, itt pedig egy javascript-java kombóvaal megvalósított számológépet.
múlthéten ezekre volt időm:
a "try-catch-finally" fejtörő a "try" sztringet írja a kimenetre. ez elsőre furcsának tűnhet, de a specifikáció megfelelő bekezdésdét elolvasva minden egyértelmű. ebből kiderül, hogy ha a try blokk egy return utasítással fejeződik be, akkor a finally blokk még lefut. ha a finally blokk normálisan ér véget (nincs return vagy kivétel), akkor a try utasítás eredménye a try blokkban visszaadott érték. ez a magyarázat a fejtörőre.
egyébként ha a finally blokkban is lenne egy return, akkor más lenne a helyzet. ekkor ugyanis az eredmény a finally blokkban visszaadott érték lenne.
mit ír ki az alábbi kód (ha egyáltalán lefordul)?
public class TryCatchFinally {
public String test() {
String result = "try";
try {
return result;
} finally {
result = "finally";
}
}
public static void main(String[] args) {
TryCatchFinally tcf = new TryCatchFinally();
System.out.println(tcf.test());
}
}

a java nyelvkezdőknek, néha haladóknak, interjú kérdések, beugratós tesztek, példakódok, újdonságok stb.
magamról
iratkozz fel!
iratkozz fel gyorsan!