Last active
August 29, 2015 14:23
-
-
Save bhuemer/f2830bed7a32eba863a5 to your computer and use it in GitHub Desktop.
Showing how to use ConcurrentMap#compute to build a ticket allocation system
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.concurrent.{ConcurrentHashMap, ConcurrentMap} | |
import java.util.function.BiFunction | |
case class Ticket(id: String) | |
case class Client(id: String) | |
case class TicketAndClient(ticket: Ticket, client: Option[Client]) | |
trait TicketSystem { | |
/** | |
* Returns `true`, if we can sell the given ticket. Note that this is by no means a guarantee that | |
* subsequent calls to [[sellTo]] will succeed, after all that ticket might be sold concurrently | |
* in the meantime. It is just an indication, which I wouldn't really recommend to use for actual | |
* business logic (other than presenting it to the user, maybe in some UI). | |
*/ | |
def isAvailable(ticket: Ticket): Boolean | |
/** | |
* Returns `true`, if we managed to allocate the given ticket to the given | |
* client, `false` otherwise (i.e. if it has been sold already). | |
*/ | |
def sellTo(ticket: Ticket, to: Client): Boolean | |
/** | |
* Returns `true`, if we managed to reallocate the given ticket to the given | |
* client, `false` otherwise (i.e. if it had been sold to someone else, or not sold at all). | |
*/ | |
def resellTo(ticket: Ticket, from: Client, to: Client): Boolean | |
/** | |
* Returns `true,` if we managed to deallocate the given ticket from the given client, | |
* `false` otherwise (i.e. if it had not been sold to him/her in the first place). | |
*/ | |
def returnTicket(ticket: Ticket, from: Client): Boolean | |
} | |
class ConcurrentTicketSystem extends TicketSystem { | |
/** In-memory table of all the tickets we have available / have sold already. */ | |
private val tickets: ConcurrentMap[String, TicketAndClient] = new ConcurrentHashMap() | |
override def isAvailable(ticket: Ticket): Boolean = { | |
val current = tickets.get(ticket.id) | |
current == null || current.client.isEmpty | |
} | |
override def sellTo(ticket: Ticket, to: Client): Boolean = { | |
update(ticket, Some(to), { | |
// Had been sold once already, but it's available again | |
case TicketAndClient(_, None) => true | |
// This ticket has never been sold yet | |
case null => true | |
// It's taken already .. | |
case _ => false | |
}) | |
} | |
override def resellTo(ticket: Ticket, from: Client, to: Client): Boolean = { | |
update(ticket, Some(to), { | |
// Only if the ticket has been sold to the previous client will we update it | |
case TicketAndClient(_, Some(previous)) => previous == from | |
// Someone else has it, or it's not sold at all | |
case _ => false | |
}) | |
} | |
override def returnTicket(ticket: Ticket, from: Client): Boolean = { | |
update(ticket, None, { | |
// Only if the ticket has been sold to that client will we return it | |
case TicketAndClient(_, Some(previous)) => previous == from | |
// Someone else has it, or it's not sold at all | |
case _ => false | |
}) | |
} | |
/** | |
* Note that `condition` really must not have any side effects as it may be called multiple times if there's | |
* concurrent writes going on, for example. | |
*/ | |
private def update(ticket: Ticket, client: Option[Client], condition: TicketAndClient => Boolean): Boolean = { | |
val update = TicketAndClient(ticket, client) | |
val current = tickets.compute(ticket.id, bi({ (ticketId, previous) => | |
if (condition(previous)) update else previous | |
})) | |
// Deliberately use reference equals here to avoid return `true` multiple times | |
// if we sell the same ticket to the same client, for example. | |
current != null && current.eq(update) | |
} | |
// to get rid of ugly syntax | |
private def bi[T, U, R](body: (T, U) => R): BiFunction[T, U, R] = new BiFunction[T, U, R] { | |
override def apply(t: T, u: U): R = body(t, u) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment