Να μου ζήσετε παιδιά μου!!! Επιτέλους ένα ωραίο και τεχνικό θεματάκι μετά απο τόσο καιρό
Λοιπόν έχουμε και λέμε (θα είναι ψιλομεγάλο το post):
Στην συγκεκριμένη περίπτωση έχεις
conflict δύο ξεχωριστών ιδιοτήτων/features.
0)Πάμε σε μερικά βασικά που πρέπει να γνωρίζεις για να το πιάσουμε. Έχεις διαφορετικά κομμάτια μνήμης για κάθε
process: το
heap, το
stack, το
code, το
data (αυτά έχουν να κάνουν με τα segment registers του x86 αλλά μην το τραβήξουμε σε assembly γιατί δεν θα το διαβάσει κανένας το post
). Όταν τρέχεις ένα .class, τότε γίνονται τα εξής*:
a)ο εκτελέσιμος κώδικας για κάθε method (οι εντολές δηλαδή), μπαίνουν στο
code segment.
b)static, final και διάφορα άλλα variables τέτοιων τύπων μπαίνουν στο
data
c)μεταφέρεται ο έλεγχος στην main και ξεκινάει η εκτέλεση -- δηλαδή η main, σαν method και αυτή είναι στο code segment και απλά πάει ο έλεγχος εκεί και ξεκινάει η εκτέλεση.**
Κάθε φορά που γίνεται ένα
new τότε πολύ απλά δεσμεύεται χώρος
μόνο για τα global variables μιας class στο
heap (τα methods βρίσκονται ήδη στο code segment δεν χρειάζεται να ξαναφορτωθούν).
Ενώ κάθε φορά που τρέχει μια method τα local variables αλλά και τα arguments περνάνε στο
stack
1)Η Java σου δίνει την δυνατότητα να χρησιμοποιείς inner classes (συντακτικά). Αυτό τι σημαίνει όσον αφορά όμως το scope/life*** των variables; Οτι θα μπορείς να χρησιμοποιήσεις κατευθείαν μια μεταβλητή που έχει οριστεί σε μια function/method μέσα στην inner class. Ο κλασσικός ορισμός του scope, nothing more, nothing less.
2)Πάμε λίγο σε σοβαρά πράγματα τώρα, για να υλοποιήσεις το variable scope γίνεται χρήση της στοίβας για τις local variables και τα ορίσματα σε μεθόδους.
Συνεπώς εάν έχεις το εξής code segment:
Code: Select all
public class foo {
private int fooBar;
public foo() {
fooBar = 0;
}
public void print() {
int fooBar = 1;
System.err.println(fooBar+" "+this.fooBar);
}
public static void main(String args[]) {
new foo().print();
}
}
Όταν κληθεί η print(), τότε έχεις στην στοίβα (μεταξύ άλλων - return addess, return value κλπ κπλ) και το fooBar--το local. Κάθε φορά που γίνεται αναφορά στο fooBar μέσα στην μέθοδο, αυτό αναφέρεται σε μια συγκεκριμένη θέση στο stack, ενώ το this.booBar είναι μια θέση της μνήμης στο heap.
Το πρόβλημα είναι ότι η ζωή του local fooBar είναι όσο διαρκεί η κλήση της method print(). Λίγο πρίν ξεκινήσει η εκτέλεση της τρέχει σε assm (το παράγει ο compiler αυτό - εσύ δεν το βλέπεις) ένα κομμάτι που κάνει stack advancement, περνάει παραμέτρους, return address κλπ κλπ. Μόλις τελειώσει πετάει και ότι βρίσκεται μέσα στο κομμάτι αυτό της στοίβας που χρησιμοποίησε (αφαιρεί απο το top δηλαδή).
3)Εσύ τώρα τι έχεις; Μια class μέσα σε μια function. Η class υπάρχει στο heap (απο την αρχή), ενώ η function έχει τις local variables στο stack. Οπότε δύο προβλήματα:
a)πως μια method (η
run()) μιας class(της inner -
TimerTask) θα προσπελάσει κάτι που βρίσκεται μέσα σε στο stack μιας μεθόδου (
func()) μιας άλλης class (αυτής στην οποία
ανήκει η func());
b)κανονικά το timertask είναι thread, αυτό σημαίνει ότι ενώ θα εκτελείται η run() του timertask ο κώδικας της func θα συνεχίσει να εκτελείται -- ψευδοπαράλληλα.
Όσον αφορά το a, το πρόβλημα σου είναι ότι θα πάρεις segmentation fault. Γιατί μια class θα πάει να πειράξει variables - code άλλης. Το ίδιο το OS δεν το επιτρέπει αυτό, συενπώς εσύ μπορείς να γράφεις έτσι στην Java αλλά ο compiler ποιός ξέρει τι σηκώνει απο πίσω και τι overhead έχει για να το επιτύχει αυτό -- στην ουσία υλοποιεί ένα light IPC - interprocess communication με shared memory μεταξύ δύο process για να το πετύχει (
το εικάζω αυτό δεν το έχω ψάξει).
Το b είναι στην ουσία αυτό που σου δημιουργεί το πρόβλημα σου. Καθώς θα τρέχει η run(), η func() θα τελειώσει και θα πρέπει να διαγράψει και απο την stack την local variable. Η run() όμως μπορεί να αναφερθεί σε αυτήν πιο μετά
. Συνεπώς γι' αυτό σου λέει κάντην final για να ξενοιάσει απο τέτοια θέματα, θα φύγει απο το stack και θα
ζεί ώστε να μην έχεις τέτοια probs. Επίσης αποφεύγεις και race conditions, σε περίπτωση που δεν κάνεις σωστό multiple access (εάν και η func και η run πειράζουν την variable str).
*:Τα γράφω λίγο απλοϊκά, εάν θέλετε το χοντρένουμε μετά, δεν είναι εντελώς έτσι εδώ.
**: Εντάξει γίνεται και κάτι ακόμη εδώ, το environment preparation, δηλαδή αντιγράφονται environment variables στο stack της method αυτής για να μπορείς να επεξεργαστείς τα arguments κλπ κλπ.
***: Το scope και το life μιας μεταβλητής είναι το ίδιο πράγμα ουσιαστικά, ανάλογα με το scope, ορίζεται και πόσο θα
ζήσει η κάθε μεταβλητή.