Skip to content

Instantly share code, notes, and snippets.

@bhuemer
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bhuemer/f2830bed7a32eba863a5 to your computer and use it in GitHub Desktop.
Save bhuemer/f2830bed7a32eba863a5 to your computer and use it in GitHub Desktop.
Showing how to use ConcurrentMap#compute to build a ticket allocation system
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