Step 1: We need to actually create the thread-safe queue to use.
Step 2: Set up all the producers (don't run them yet, we don't want threads to start running before all are in place)
Step 3: Set up all the consumers. We can start them running now, since both producers and consumers are ready. Since the queues are as of yet, empty, this isn't an issue. Seeing an empty queue, the consumers will yield.
Step 4: Start the producers.
Step 5: Wait for the producers to finish. Once they are, no new values are going to be added to the queue.
Step 6: Wait for the consumers to finish, since with the producers done, this signals the end of the program.
Step 7: We're done! Just print out the results so we can see what happened.
Everything is much simpler when you get some blueprints in first :) Here's the code:
That's about half of it, right there. So let's go through it, by line!
L 84-85: I just spread the method declaration over 2 lines here, so I could fit the entire line in the image. Remember, whitespace isn't important in Java, so you can do this.
L 88-90: These lines are where we create our Queue for use. Kind of messy, but compressed to 1 statement, so I figured it was worth it. Reads: Create a new queue, which is synchronized. In short, at least.
L 92-95: These lines will set up all our producers, without starting to run any of them. Arrays are generally used over ArrayLists or whatever because they're simple and easy to understand. You don't need much flexibility here, since we won't be adding or removing producers.
L 97-101: Here, we create our array of consumers, give them all values, then start each one running. As mentioned in the overview, although the scheduler starts scheduling the threads, upon seeing an empty queue, they'll just yield.
L 103-105: Now that we have our consumers running, we can start with the producers, so that's what these lines are for. Remember the for-each loop? Makes things easier :P
Phew, this is huge. Was worse, because I commented my code (which you can find in the source, posted below), and let me tell you, its much harder when you comment your code, because you have to know what stuff does. Trial and error is great and all, but you can't just say: "Well, I fucked around with some stuff, and then it worked". Anyway:
L 107-113: As per the overview, we're now waiting for threads to end. Specifically, in these lines, the producers. The join method causes the thread executing the code to wait for the termination of the object being joined to. Basically, join() waits for the thread to die. It throws that exception, too, as Netbeans so kindly informed me. I've not seen it occur yet, but whatever.
L 115-117: Now that the producers are done producing, we know that the queue being empty means we're done, nothing else is coming. So we let them know its time to finish.
L 119-125: Now we just sit back and wait for the consumers to empty the queue. Same deal as waiting for the producers.
L 127-133: Now that everything is done, this little chunk of code just prints off what each consumer actually got out of it, so we know if the program worked.
My fingers hurt from all the typing :( But there's still 1 more method to do! The main method!
At least THAT was simple. You can try with whatever numbers you want. I had 10 producers, 10 consumers and 1 item per producer, for simplicity's sake. I actually tried with a lot more items, but holy crap I didn't think that through. So much spam.
Since the order of threads is non-deterministic, everyone should get a different output. Different each time you run it, even. Here's an example output:
Consumer: 0 contains: [70000]
Consumer: 1 contains: [40000]
Consumer: 2 contains: [null, 80000, 90000]
Consumer: 3 contains: [null]
Consumer: 4 contains: [30000]
Consumer: 5 contains: [20000]
Consumer: 6 contains: [60000]
Consumer: 7 contains: [0]
Consumer: 8 contains: [10000]
Consumer: 9 contains: [50000]
The program should actually output each time a Producer/Consumer adds or removes an element, but that's a bit messy, so here's just the end of the output.
We can see that a couple of times, a null enters itself into the consumers' list. We'd probably have to make sure the value isn't null before adding it, but that's a small fix, and I'll be damned if I'm going to go back now.
Another thing to notice is that the distribution of actual values is perhaps not fair. Consumer 2 gets 2 values, and Consumer 3 is left empty handed.
So there we have it, an almost perfect synched queue. Just add a conditional saying null isn't allowed, and we're golden.
....Goddamn. Tomorrow, I think I'll just bullshit around with random coding stuff instead of continuing with multithreading, I need some time to let it sink in and stuff, so maybe a fun little program ahead! See you tomorrow.
This was great.
ReplyDeleteThis was complex! Thanks!
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteYou used to word simple so often but I'm sorry, this is a touch difficult for me to follow still.
ReplyDeleteNot your fault though, I just need to stare at it a bit more.