As was foretold, we've added advertisements to the forums! If you have questions, or if you encounter any bugs, please visit this thread: https://forums.penny-arcade.com/discussion/240191/forum-advertisement-faq-and-reports-thread/

SELECT * FROM posts WHERE tid = 'PA PROGRAMMING THREAD'

18283858788100

Posts

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    Exercise for you. See if you can flip that around in to a LILO queue using synchronization primitives. Mutexs, spinlocks, events. Not really sure what Java exposes, it's been years, but those are your next adventure in threading.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • Monkey Ball WarriorMonkey Ball Warrior A collection of mediocre hats Seattle, WARegistered User regular
    edited May 2012
    Ugh. Sync primatives are unpleasant. I prefer just using blocking queues for everything. But that might be because I learned synchronization in Go first.

    In fact when I had to write a scheduler simulator using Pthreads in C in my operating systems class, the first thing I did was write a blocking queue for ints so I could pass messages between threads.

    EDIT: Here it is, actually. It might not actually compile because I just merged the header into it, but you can get the basic idea.
    #define TRUE -1
    #define FALSE 0
    
    #include<stdlib.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    typedef struct queue_tag {
        int *array; //The circular array
        int head; //The first element of contents
        int tail; //The next empty element
        int cap;   //The size of the circular array
        pthread_mutex_t *lock; //a mutex on this queue
        sem_t *size_sem; //the number of elements, == size
        sem_t *empty_sem; //the room left in the array, cap - size
    } queue_str, *queue_t;
    
    /** Creates a new BlockingQueue of the given capacity **/
    queue_t queue_init(const int capacity) {
        queue_t queue = malloc(sizeof(queue_str));
        queue->cap = capacity;
        queue->array = malloc(sizeof(int) * capacity);
        queue->head = 0;
        queue->tail = 0;
        queue->lock = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
        queue->empty_sem = (sem_t *) malloc(sizeof(sem_t));
        queue->size_sem = (sem_t *) malloc(sizeof(sem_t));
        pthread_mutex_init(queue->lock, NULL);
        sem_init(queue->empty_sem, 0, capacity);
        sem_init(queue->size_sem, 0, 0);
        return queue;
    }
    
    //Frees all memory associated with queue
    void queue_destroy(queue_t queue) {
        free(queue->array);
        pthread_mutex_destroy(queue->lock);
        sem_destroy(queue->empty_sem);
        sem_destroy(queue->size_sem);
        free(queue->lock);
        free(queue->empty_sem);
        free(queue->size_sem);
        free(queue);
    }
    
    //Adds toAdd to the queue, blocking if full.
    void queue_put(const int toAdd, queue_t queue) {
        sem_wait(queue->empty_sem); //wait for room
    
        pthread_mutex_lock(queue->lock);
        queue->array[queue->tail] = toAdd;
        queue->tail = (queue->tail + 1) % queue->cap;
        pthread_mutex_unlock(queue->lock);
    
        sem_post(queue->size_sem); //announce new contents
    }
    
    //Returns the next item in the queue, blocking if empty.
    int queue_get(queue_t queue) {
        sem_wait(queue->size_sem); //wait for contents
    
        pthread_mutex_lock(queue->lock);
        int val = queue->array[queue->head];
        queue->head = (queue->head + 1) % queue->cap;
        pthread_mutex_unlock(queue->lock);
    
        sem_post(queue->empty_sem); //announce room made
        return val;
    }
    
    // Attempts to add to the queue
    // returns TRUE if sucsessful, FLASE if not
    int queue_tryput(const int toAdd, queue_t queue) {
        if (sem_trywait(queue->empty_sem)) {
        	return FALSE;
        }
        pthread_mutex_lock(queue->lock);
        queue->array[queue->tail] = toAdd;
        queue->tail = (queue->tail + 1) % queue->cap;
        pthread_mutex_unlock(queue->lock);
    
        sem_post(queue->size_sem); //announce new contents
        return TRUE;
    }
    
    // Attempts to dequeue the next item in the queue, setting it in the given address
    // returns TRUE if sucsessful, FLASE if not
    int queue_tryget(int * value, queue_t queue) {
        if (sem_trywait(queue->size_sem)) {
        	return FALSE;
        }
    
        pthread_mutex_lock(queue->lock);
        *value = queue->array[queue->head];
        queue->head = (queue->head + 1) % queue->cap;
        pthread_mutex_unlock(queue->lock);
    
        sem_post(queue->empty_sem); //announce room made
        return TRUE;
    }
    

    Monkey Ball Warrior on
    "I resent the entire notion of a body as an ante and then raise you a generalized dissatisfaction with physicality itself" -- Tycho
  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    Blocking queues are fine on smaller scales, but when doing high throughput multithreaded computing, become completely inefficient and bothersome. There are certainly frameworks and languages out there that hide synchronization from you, but I still think it's a fundamental piece of knowledge to do serious parallel programming on x86 processors. Certainly on more super parallel systems, things are can be different. You don't have to use them all the time, especially in languages like C# and Java which offer framework and language level sugars for that stuff, but understanding them helps to properly use those high level tools.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • urahonkyurahonky Registered User regular
    Really wish I learned about threads in college. They sorta talked about them and then moved on. So now I'm really gun shy. @GnomeTank I've never heard of Mutexs, spinlocks, or events for threads.

    The Objectives for the Java exam (in regards to Threads) are:
    Write code to define, instantiate, and start new threads using both java.lang.Thread and java.lang.Runnable.
    Recognize the states in which a thread can exist, and identify way sin which a thread can transition from one state to another.
    Given a scenario, write code that makes appropriate use of object locking to protect static or instance variables from concurrent access problems.
    Given a scenario, write code that makes appropriate use of wait, notify, or notifyAll.

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    Object locking is a form of synchronization honky. Though it seems like the Java exam just wants you to know the highest level, and not the lower level primitives. I would still at least look up what I am talking about.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • Monkey Ball WarriorMonkey Ball Warrior A collection of mediocre hats Seattle, WARegistered User regular
    edited May 2012
    GnomeTank wrote: »
    Blocking queues are fine on smaller scales, but when doing high throughput multithreaded computing, become completely inefficient and bothersome. There are certainly frameworks and languages out there that hide synchronization from you, but I still think it's a fundamental piece of knowledge to do serious parallel programming on x86 processors. Certainly on more super parallel systems, things are can be different. You don't have to use them all the time, especially in languages like C# and Java which offer framework and language level sugars for that stuff, but understanding them helps to properly use those high level tools.

    I think I can agree with that. Knowing how to use them, and actually using them when easier structures will work are two different things.
    urahonky wrote: »
    Really wish I learned about threads in college. They sorta talked about them and then moved on. So now I'm really gun shy. @GnomeTank I've never heard of Mutexs, spinlocks, or events for threads.

    The Objectives for the Java exam (in regards to Threads) are:
    Write code to define, instantiate, and start new threads using both java.lang.Thread and java.lang.Runnable.
    Recognize the states in which a thread can exist, and identify way sin which a thread can transition from one state to another.
    Given a scenario, write code that makes appropriate use of object locking to protect static or instance variables from concurrent access problems.
    Given a scenario, write code that makes appropriate use of wait, notify, or notifyAll.

    I would say the two most important concepts here are mutexes and semaphores. Spinlocks are more of an antipattern, and events are language-specific abstractions of simple message passing between threads.

    Monkey Ball Warrior on
    "I resent the entire notion of a body as an ante and then raise you a generalized dissatisfaction with physicality itself" -- Tycho
  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    @Urahonky: so I went and looked, and apparently Java doesn't even expose threading primitives? It's all done through the synchronized keyword it seems, which is what I think they mean by object locking. The rest is all just waits and notifys. Notify is essentially a high level interface for events, and synchronized is a high level interface for a monitor. I don't see any direct mutex support. No spinlocks at all really.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • urahonkyurahonky Registered User regular
    edited May 2012
    Yeah synchronizing an object places a lock on it when a Thread enters. And when you do notify() it releases that lock for every Thread waiting on that object. For example, some really good code my coworker gave me:
    public static void main(String[] args) {
            (new Thread(new Question6(0))).start();
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Start");
            synchronized (this) {
                if (index<2) {
                    index++;
                    (new Thread(this)).start();
                    try {
                        wait();
                    } catch (InterruptedException e) {}
                } else {
                    notifyAll();
                }
            }
            System.out.println(Thread.currentThread().getName() + " End");
        }
    

    urahonky on
  • Monkey Ball WarriorMonkey Ball Warrior A collection of mediocre hats Seattle, WARegistered User regular
    A lot of useful stuff is in the java.util.concurrent package.

    "I resent the entire notion of a body as an ante and then raise you a generalized dissatisfaction with physicality itself" -- Tycho
  • urahonkyurahonky Registered User regular
    Basically Thread-0 enters, increments index, then spawns another Thread then waits, that Thread enters, increments index, and then spawns another Thread, then waits, and then that final Thread enters, calls notifyAll();, and then hits the system.out.println, and the rest follow.

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    That essentially spools up threads, until it's spooled up two, each one doing a wait. The final one started will notify the rest to unblock, and they all finish at the same time...well, as same as possible... temporal disassociation and all that. It's non-deterministic though. You're not guaranteeing any sort of end order that way.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • urahonkyurahonky Registered User regular
    Yeah it's really hard to see the various output when it moves so fast. I get Thread-2 Thread-1 Thread-0 every time, but that's not necessarily going to happen every time.

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    edited May 2012
    Right. It's likely to happen every time. But it's not deterministic, it could happen in a different order. A lot of times those type of temporal disassociation issues won't show up until the software is put under strain on a production system. It's best to learn to understand them and know when using other forms of synchronization to force ordering is correct.

    GnomeTank on
    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    It can be pretty tricky to think about code in parallel, but it helps a lot. I find I do a lot with thread pools these days

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    Phyphor wrote: »
    It can be pretty tricky to think about code in parallel, but it helps a lot. I find I do a lot with thread pools these days

    Yeah, I use the ever living shit out of the Tasks library in .NET, which is a fancy scheduled thread pool. It's not for the most complex threading issues, but for worker-task style stuff, it's phenomenal.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • HalibutHalibut Passion Fish Swimming in obscurity.Registered User regular
    Java 5 introduced lots of cool stuff in the concurrent package. Take a look at the Executors class. It has a bunch of static factory methods for creating ExecutorServices (Thread Pools). The most useful method in the ExecutorService interface is submit(Callable<T> task). It returns a Future<T> which is basically a reference to the yet-to-be-completed Callable. You can pass the Future around, and it blocks only when you try to get the value out of it (assuming the task hasn't finished yet).

    You still have to worry about shared state and all that, but it makes it easier to reason about parallel tasks. I normally try to design the concurrent part of my apps to not share any state and then combine the results back together when it's running in a single thread again. It's not always possible, but it makes it 1000 times easier if you don't have to worry about locks and synchronization.

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    That's basically what I do. I will have the worker thread work completely local, and then when that thread is complete, I will merge the data either by locking the final store, or doing it on the primary thread of execution. In our UI framework, we actually have a static class with some static methods that allow us to easily defer code back to the UI thread, allowing our logic to flow consistently, but making sure we keep certain sections of code nice and thread safe.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • HalibutHalibut Passion Fish Swimming in obscurity.Registered User regular
    On an unrelated topic, I recently found a plugin for Eclipse called Infinitest. It's basically a continuous unit test runner. Make a change to any code, and it finds affected tests (I'm assuming via some kind of static code analysis) and runs them immediately. Really useful to know right away when a code change causes something to break. It's also forcing me to make sure stuff is loosely coupled. Normally any code change causes 2-3 tests to run, but some of the older stuff in my project has like 10 tests running with any change.

  • urahonkyurahonky Registered User regular
    I don't like this, but hopefully I'll remember it if I type this out to you guys:
    Thread t = new Thread();
    try{
       t.sleep(5000);
        } catch(Exception e){}
    

    A very, very simple example obviously. But the t.sleep(5000) doesn't actually get called on the t Thread, but on the currently running Thread.

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    edited May 2012
    Because that's what sleep does. It blocks the current thread. Yes, it's illogical. This is actually why Sleep is a static method on .NET's Thread. To achieve what you want, just call sleep(5000) in your Thread's run method.

    e: I should clarify: The fact that sleep blocks the current thread is not illogical, it's required. You can't sleep a thread at some random instruction from outside that thread. The fact that sleep is an instance method, implying that you can call it on a thread object that represents a thread different than the call site, is illogical. While you can call it, it's just going to block the current thread, which is likely not the correct behavior. I know why it's an instance method, but it should be protected, not public.

    GnomeTank on
    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • urahonkyurahonky Registered User regular
    GnomeTank wrote: »
    Because that's what sleep does. It blocks the current thread. Yes, it's illogical. This is actually why Sleep is a static method on .NET's thread. To achieve what you want, just call sleep(5000) in your Thread's run method.

    Yeah it just basically caused my brain to shut down for a second or two. And I do believe that sleep is a static method in Java as well.

  • HalibutHalibut Passion Fish Swimming in obscurity.Registered User regular
    sleep() is static in java too. You are probably getting a warning about invoking a static method as if it were an instance method (I'm not sure why Java allows this at all, but at least it gives a warning).

  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    Well, it does allow the syntactic sugar of being able to say 'sleep(5000)' inside a thread sub-class, rather than having to do this. or Thread.? I guess? I would say the confusion it causes is probably not worth that shortcut, but I'm not used to it either. If I switched back to Java, it would mess with me for a while.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • HalibutHalibut Passion Fish Swimming in obscurity.Registered User regular
    That's true I guess. But for a language that lacks a lot of syntactic sugar, it definitely feels out of place. If you really need the sugar, you can just do a static import on the method (as of Java 5). Probably some legacy thing left over from the early days that Sun/Oracle can't fix due to compatibility issues.

    I agree that the confusion is not worth it.

  • SaerisSaeris Borb Enthusiast flapflapflapflapRegistered User regular
    edited May 2012
    I remember writing a C program to find primes that used pthread mutexes for synchronization. It took about 20 hours of staring at the problem before something magically clicked and I instantly understood multithreaded programming. I don't get a whole lot of those genuine eureka moments, but that was definitely one. It's made me a better developer in general.

    I also found out that an operating system gets cranky when you spawn over 1000 threads. Although, when you are running that many, at least it's easy to spot nondeterministic behaviour.


    edit: I ended up encapsulating the synchronization in a dinky little C-pseudoclass called Mailbox which just holds a single int. This was years ago, but it still seems remarkably readable. Here it is:
    #include <pthread.h>
    
    typedef struct {
    	int value;
    	int hasValue;
    	int closed;
    	pthread_mutex_t lock;
    	pthread_cond_t susp;
    } mailbox_t;
    
    
    mailbox_t* mailbox_create() {
    	mailbox_t* this = (mailbox_t*)malloc(sizeof(mailbox_t));
    	pthread_mutex_init(&this->lock, NULL);
    	pthread_cond_init(&this->susp, NULL);
    	this->closed = 0;
    	this->hasValue = 0;
    	return this;
    }
    
    
    void mailbox_close(mailbox_t* this) {
    	pthread_mutex_lock(&this->lock);
    	this->closed = 1;
    	pthread_cond_signal(&this->susp);
    	pthread_mutex_unlock(&this->lock);
    }
    
    
    void mailbox_waitForClose(mailbox_t* this) {
    	pthread_mutex_lock(&this->lock);
    	while (!this->closed) {
    		pthread_cond_wait(&this->susp, &this->lock);
    	}
    	pthread_mutex_unlock(&this->lock);
    }
    
    
    void mailbox_enter(mailbox_t* this, int value) {
    	pthread_mutex_lock(&this->lock);
    	while (this->hasValue) {
    		pthread_cond_wait(&this->susp, &this->lock);
    	}
    	this->hasValue = 1;
    	this->value = value;
    	pthread_cond_signal(&this->susp);
    	pthread_mutex_unlock(&this->lock);
    }
    
    
    int mailbox_remove(mailbox_t* this) {
    	int value;
    	pthread_mutex_lock(&this->lock);
    	while (!this->hasValue) {
    		pthread_cond_wait(&this->susp, &this->lock);
    	}
    	value = this->value;
    	this->hasValue = 0;
    	pthread_cond_signal(&this->susp);
    	pthread_mutex_unlock(&this->lock);
    	return value;
    }
    

    Saeris on
    borb_sig.png
  • ecco the dolphinecco the dolphin Registered User regular
    edited May 2012
    Saeris wrote: »
    edit: I ended up encapsulating the synchronization in a dinky little C-pseudoclass called Mailbox which just holds a single int. This was years ago, but it still seems remarkably readable. Here it is:

    Easily readable! Good stuff for a 20 hour Eureka moment!

    Just a note if you do something like this again, because you were this close to having a bug in your mailbox_waitForClose(), enter(), and remove() functions.

    Specifically, the looping on while( this->variable ) should probably have been something like:
    void mailbox_waitForClose(mailbox_t* this) {
    	volatile int *pThisClosed = &this->closed; // volatile because this->closed is designed to be changed by another thread in another function
    	pthread_mutex_lock(&this->lock);
    	while (!*pThisClosed) {
    		pthread_cond_wait(&this->susp, &this->lock);
    	}
    	pthread_mutex_unlock(&this->lock);
    }
    

    To see the problem, you need to keep in mind that the optimising C/C++ compiler implicitly assumes single threaded operation. With that in mind, have a look at this simplified loop:
    while (!this->closed) {}
    

    The pseudo-assembly for this loop would have gone something like:
    startOfLoop:
    load this->closed into CPU register Rx
    compare Rx to 0
    if equal to 0, go to startOfLoop
    // Loop exits here
    

    The optimising compiler will assume single threaded operation by default and say "Hang on! Nothing in this while loop changes the value of this->closed! Why bother reloading something from memory that couldn't possibly have changed?" This will produce optimised code like:
    load this->closed into CPU register Rx
    startOfLoop: // Move start of loop down to reduce the number of memory accesses
    compare Rx to 0
    if equal to 0, go to startOfLoop
    // Loop exits here
    

    It can even go one step further:
    load this->closed into CPU register Rx
    compare Rx to 0
    if not equal to 0, go to exitLoop
    infiniteLoop:
    go to infiniteLoop
    exitLoop:
    // Loop exits here
    

    In other words - you'll end up with an infinite loop. A very very common symptom of this bug is that things will work fine in debug mode with no optimisations, but when optimisations are turned on, calling these functions will hang in these loops.

    Now, I believe where you may have avoided the bug was by calling pthread_cond_wait(), because the compiler is unable to prove that pthread_cond_wait() will leave this->closed untouched. In other words, when it hits the loop, it says, "Hmm... calling pthread_cond_wait() might change the value of this->closed, so I should keep the memory access.". I am not certain about how far the compiler has to go to prove to itself that there are no aliases to variables in functions being called - this bit here is me guessing.

    If my guess is true, then this is probably because pthread_cond_wait() is externally linked, and so the compiler cannot (will not?) statically analyse it. If pthread_cond_wait() was replaced by one of your internal functions, certain compilers are able to do full programme optimisations and prove that this->closed will remain untouched, and optimise to the infinite loop above. (Alternatively, the compiler might have to meet a lesser burden of proof than I was expecting).

    Adding the 'volatile' keyword explicitly tells the compiler that the variable can change at any time for whatever reason (in your case, due to another thread). This means that the optimising compiler should not cache the variable in a CPU register, and so prevent the infinite loop optimisation.

    ecco the dolphin on
    Penny Arcade Developers at PADev.net.
  • jaziekjaziek Bad at everything And mad about it.Registered User regular
    edited May 2012
    Man this sucks. I'm finishing up the report for the project I was doing in march, and I'm just staring at my code wondering why the hell I ever though doing it like this was a good idea. I mean, I still understand what was going on, but it is just SO hard to read, and makes absolutely no sense.

    Like, you can tell I was just doing it on the fly and had no real plan, and got halfway and kind of wanted to go back and make it all better but there wasn't time and... blegh...

    I have a function which is 200+ lines of nested if statements. Good god.

    jaziek on
    Steam ||| SC2 - Jaziek.377 on EU & NA. ||| Twitch Stream
  • EtheaEthea Registered User regular
    jaziek wrote: »
    Man this sucks. I'm finishing up the report for the project I was doing in march, and I'm just staring at my code wondering why the hell I ever though doing it like this was a good idea. I mean, I still understand what was going on, but it is just SO hard to read, and makes absolutely no sense.

    Like, you can tell I was just doing it on the fly and had no real plan, and got halfway and kind of wanted to go back and make it all better but there wasn't time and... blegh...

    I have a function which is 200+ lines of nested if statements. Good god.

    Don't feel bad I have seen production code that is thousand's of lines of nested if statements that created a poor file syntax parser.

  • urahonkyurahonky Registered User regular
    So I'm studying up on Comparators vs Comparables. It seems Comparables allow you to implement public int compareTo(Object o1), whereas Comparator allows you to implement public int compare(Object o1, Object o2). And I keep reading that you can have multiple Comparators whereas you can only have one Comparable. How does that work?

    Does that just mean I could do:
    Person person = new Person("Asshole",24);
    Person person2 = new Person("Poopybutt", 1);
    ArrayList<Person> personList = new ArrayList<Person();
    personList.add(person);
    personList.add(person2);
    Collections.sort(personList, new NameComparator());
    System.out.println(personList);
    Collections.sort(personList, new AgeComparator());
    System.out.println(personList);
    

    Somehow I keep reading that as you can do something like:

    Collections.sort(personList, newNameComparator(), new AgeComparator());

    But I know that's wrong. Is that the only benefit of Comparator vs Comparable? I think I MAY have just answered my question.

  • urahonkyurahonky Registered User regular
    edited May 2012
    Alright now I'm confused again. I'm working with Generics and Collections now. Let's say I have the following classes:
    class Animal {
        @Override
        public String toString(){
        return this.getClass().getSimpleName();
    }}
    class Dog extends Animal{}
    class Cat extends Animal{}
    class Truck{}
    

    And the following method:
    public static void go(ArrayList<? extends Animal> list){
            
            for(Object o : list.toArray()){
                System.out.println(o.getClass().getSimpleName());
            }
        }
    

    Why does the following still work?
    ArrayList animalList = new ArrayList();
            animalList.add(new Animal());
            animalList.add(new Dog());
            animalList.add(new Cat());
            animalList.add(new Truck());
            
            go(animalList);
    

    Truck doesn't extend Animal, so it should've at least given me a warning or something right? It happens even if I change the ArrayList to:

    ArrayList<Animal> animalList = new ArrayList<Animal>(); (Which surprised me even more since Truck isn't even an Animal... I didn't think it would allow me to do that.)

    urahonky on
  • centraldogmacentraldogma Registered User regular
    edited May 2012
    urahonky wrote: »
    Alright now I'm confused again. I'm working with Generics and Collections now. Let's say I have the following classes:
    class Animal {
        @Override
        public String toString(){
        return this.getClass().getSimpleName();
    }}
    class Dog extends Animal{}
    class Cat extends Animal{}
    class Truck{}
    

    And the following method:
    public static void go(ArrayList<? extends Animal> list){
            
            for(Object o : list.toArray()){
                System.out.println(o.getClass().getSimpleName());
            }
        }
    

    Why does the following still work?
    ArrayList animalList = new ArrayList();
            animalList.add(new Animal());
            animalList.add(new Dog());
            animalList.add(new Cat());
            animalList.add(new Truck());
            
            go(animalList);
    

    Truck doesn't extend Animal, so it should've at least given me a warning or something right? It happens even if I change the ArrayList to:

    ArrayList<Animal> animalList = new ArrayList<Animal>(); (Which surprised me even more since Truck isn't even an Animal... I didn't think it would allow me to do that.)

    I’m guessing it’s because your arraylist isn’t the generic version, so it’s storing everything as an Object.

    Forgetting my syntax but something like:
    ArrayList <? extends Animal> animalList = new  ArrayList <? extends Animal> ();
            animalList.add(new Animal());
            animalList.add(new Dog());
            animalList.add(new Cat());
            animalList.add(new Truck());
            
            go(animalList);
    

    centraldogma on
    When people unite together, they become stronger than the sum of their parts.
    Don't assume bad intentions over neglect and misunderstanding.
  • wildwoodwildwood Registered User regular
    @urahonky - about Comparable and Comparator - in general, a class implements Comparable because it's supposed to be sorted a particular way all the time. So, the Comparable order scopes with the object definition. Comparators, on the other hand, scope with a particular initialized data structure (like java.util.TreeSet, which can take a Comparator as part of the constructor), or with a static call to Collections or Arrays.

    I think what they mean by 'multiple Comparators' is that a single class can have multiple Comparators associated with it, such as sorting on different fields, or in ascending or descending order, while a class can only implement Comparable one way, and is thus stuck with a particular order.

    So, if you're relying on Comparable, then you can only call:
    Collections.sort(personList);
    

    But with Comparator, you can also call:
    Collections.sort(personList, new NameComparator());
    Collections.sort(personList, new AgeComparator());
    

    Does that help?

  • urahonkyurahonky Registered User regular
    edited May 2012
    That does. Thank you @wildwood !

    And it looks like I'm going to spend longer on Generics / Collections than I'd previously expected. Thanks for the heads up @centraldogma

    urahonky on
  • wildwoodwildwood Registered User regular
    urahonky wrote: »
    Alright now I'm confused again. I'm working with Generics and Collections now. Let's say I have the following classes:

    ...

    Truck doesn't extend Animal, so it should've at least given me a warning or something right? It happens even if I change the ArrayList to:

    ArrayList<Animal> animalList = new ArrayList<Animal>(); (Which surprised me even more since Truck isn't even an Animal... I didn't think it would allow me to do that.)

    Are you sure about that last part? When I try that out, I get a compile error adding Truck.

  • HalibutHalibut Passion Fish Swimming in obscurity.Registered User regular
    Comparators also give you the option of sorting things against some object or data that's not in the collection. For instance, say you wanted to sort Planet instances by their distance from some fixed point in space. You could define a Comparator that references that point, and then when you sort the planets you could calculate the distance between the 2 instances and the point to figure out the order.

    This example probably isn't so great since you'd be calculating distances N times for each planet, but hopefully that makes the idea somewhat clear.

  • urahonkyurahonky Registered User regular
    I swear it wasn't throwing an error before. Oh that's good then. I thought I was going crazy! Thanks for making me retry it. :P

  • urahonkyurahonky Registered User regular
    About 45 minutes until I take the exam again. I feel more confident than I was before... But I'm still scared shitless. I don't want to be out $300 again.

  • SaerisSaeris Borb Enthusiast flapflapflapflapRegistered User regular
    edited May 2012
    snip!
    Adding the 'volatile' keyword explicitly tells the compiler that the variable can change at any time for whatever reason (in your case, due to another thread). This means that the optimising compiler should not cache the variable in a CPU register, and so prevent the infinite loop optimisation.

    Good call! As a class assignment, this never had optimizations enabled, so that must be why that problem never appeared. That class was about operating systems in general rather than concurrent programming in particular, so threading in C was not covered extensively. I've known about this particular problem (and the need for volatile) for a while, but I must have learned about it some time after I wrote this. Your explanation is very clear though; that's a good way to explain the need for the qualifier.

    Saeris on
    borb_sig.png
  • GnomeTankGnomeTank What the what? Portland, OregonRegistered User regular
    Volatile is super handy, but also super dangerous. People need to be smart about it's usage. You should know exactly what you are trying to accomplish by using volatile.

    Sagroth wrote: »
    Oh c'mon FyreWulff, no one's gonna pay to visit Uranus.
    Steam: Brainling, XBL / PSN: GnomeTank, NintendoID: Brainling, FF14: Zillius Rosh SFV: Brainling
  • InfidelInfidel Heretic Registered User regular
    GnomeTank wrote: »
    Volatile is super handy, but also super dangerous. People need to be smart about it's usage. You should know exactly what you are trying to accomplish by using volatile.

    "volatile" is simply the "never optimize" flag.

    Obviously it is a poor decision to throw it around willy-nilly. :lol:

    OrokosPA.png
This discussion has been closed.