Avoid synchronizing on this and use an internal monitor instead
Where possible, code that previously synchronized on this (or on the class in the case of static methods) has been updated to use an internal monitor object instead. This allows the locking model that's employed to be an implementation detail rather than part of the class's API. Classes that override a synchronized method continue to declare the overriding method as synchronized. This ensures that locking is consistent across the superclass and its subclass. Closes gh-6262
This commit is contained in:
@@ -28,15 +28,22 @@ public class Snake {
|
||||
|
||||
private static final int DEFAULT_LENGTH = 5;
|
||||
|
||||
private final Deque<Location> tail = new ArrayDeque<Location>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final int id;
|
||||
|
||||
private final WebSocketSession session;
|
||||
|
||||
private Direction direction;
|
||||
private int length = DEFAULT_LENGTH;
|
||||
private Location head;
|
||||
private final Deque<Location> tail = new ArrayDeque<Location>();
|
||||
private final String hexColor;
|
||||
|
||||
private Direction direction;
|
||||
|
||||
private int length = DEFAULT_LENGTH;
|
||||
|
||||
private Location head;
|
||||
|
||||
public Snake(int id, WebSocketSession session) {
|
||||
this.id = id;
|
||||
this.session = session;
|
||||
@@ -51,43 +58,49 @@ public class Snake {
|
||||
this.length = DEFAULT_LENGTH;
|
||||
}
|
||||
|
||||
private synchronized void kill() throws Exception {
|
||||
resetState();
|
||||
sendMessage("{'type': 'dead'}");
|
||||
private void kill() throws Exception {
|
||||
synchronized (this.monitor) {
|
||||
resetState();
|
||||
sendMessage("{'type': 'dead'}");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void reward() throws Exception {
|
||||
this.length++;
|
||||
sendMessage("{'type': 'kill'}");
|
||||
private void reward() throws Exception {
|
||||
synchronized (this.monitor) {
|
||||
this.length++;
|
||||
sendMessage("{'type': 'kill'}");
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendMessage(String msg) throws Exception {
|
||||
this.session.sendMessage(new TextMessage(msg));
|
||||
}
|
||||
|
||||
public synchronized void update(Collection<Snake> snakes) throws Exception {
|
||||
Location nextLocation = this.head.getAdjacentLocation(this.direction);
|
||||
if (nextLocation.x >= SnakeUtils.PLAYFIELD_WIDTH) {
|
||||
nextLocation.x = 0;
|
||||
}
|
||||
if (nextLocation.y >= SnakeUtils.PLAYFIELD_HEIGHT) {
|
||||
nextLocation.y = 0;
|
||||
}
|
||||
if (nextLocation.x < 0) {
|
||||
nextLocation.x = SnakeUtils.PLAYFIELD_WIDTH;
|
||||
}
|
||||
if (nextLocation.y < 0) {
|
||||
nextLocation.y = SnakeUtils.PLAYFIELD_HEIGHT;
|
||||
}
|
||||
if (this.direction != Direction.NONE) {
|
||||
this.tail.addFirst(this.head);
|
||||
if (this.tail.size() > this.length) {
|
||||
this.tail.removeLast();
|
||||
public void update(Collection<Snake> snakes) throws Exception {
|
||||
synchronized (this.monitor) {
|
||||
Location nextLocation = this.head.getAdjacentLocation(this.direction);
|
||||
if (nextLocation.x >= SnakeUtils.PLAYFIELD_WIDTH) {
|
||||
nextLocation.x = 0;
|
||||
}
|
||||
if (nextLocation.y >= SnakeUtils.PLAYFIELD_HEIGHT) {
|
||||
nextLocation.y = 0;
|
||||
}
|
||||
if (nextLocation.x < 0) {
|
||||
nextLocation.x = SnakeUtils.PLAYFIELD_WIDTH;
|
||||
}
|
||||
if (nextLocation.y < 0) {
|
||||
nextLocation.y = SnakeUtils.PLAYFIELD_HEIGHT;
|
||||
}
|
||||
if (this.direction != Direction.NONE) {
|
||||
this.tail.addFirst(this.head);
|
||||
if (this.tail.size() > this.length) {
|
||||
this.tail.removeLast();
|
||||
}
|
||||
this.head = nextLocation;
|
||||
}
|
||||
this.head = nextLocation;
|
||||
}
|
||||
|
||||
handleCollisions(snakes);
|
||||
handleCollisions(snakes);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCollisions(Collection<Snake> snakes) throws Exception {
|
||||
@@ -104,29 +117,37 @@ public class Snake {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Location getHead() {
|
||||
return this.head;
|
||||
}
|
||||
|
||||
public synchronized Collection<Location> getTail() {
|
||||
return this.tail;
|
||||
}
|
||||
|
||||
public synchronized void setDirection(Direction direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public synchronized String getLocationsJson() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(this.head.x),
|
||||
Integer.valueOf(this.head.y)));
|
||||
for (Location location : this.tail) {
|
||||
sb.append(',');
|
||||
sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(location.x),
|
||||
Integer.valueOf(location.y)));
|
||||
public Location getHead() {
|
||||
synchronized (this.monitor) {
|
||||
return this.head;
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Location> getTail() {
|
||||
synchronized (this.monitor) {
|
||||
return this.tail;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDirection(Direction direction) {
|
||||
synchronized (this.monitor) {
|
||||
this.direction = direction;
|
||||
}
|
||||
}
|
||||
|
||||
public String getLocationsJson() {
|
||||
synchronized (this.monitor) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(this.head.x),
|
||||
Integer.valueOf(this.head.y)));
|
||||
for (Location location : this.tail) {
|
||||
sb.append(',');
|
||||
sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(location.x),
|
||||
Integer.valueOf(location.y)));
|
||||
}
|
||||
return String.format("{'id':%d,'body':[%s]}", Integer.valueOf(this.id),
|
||||
sb.toString());
|
||||
}
|
||||
return String.format("{'id':%d,'body':[%s]}", Integer.valueOf(this.id),
|
||||
sb.toString());
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
|
||||
@@ -33,29 +33,35 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class SnakeTimer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SnakeTimer.class);
|
||||
|
||||
private static Timer gameTimer = null;
|
||||
|
||||
private static final long TICK_DELAY = 100;
|
||||
|
||||
private static final Object MONITOR = new Object();
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SnakeTimer.class);
|
||||
|
||||
private static final ConcurrentHashMap<Integer, Snake> snakes = new ConcurrentHashMap<Integer, Snake>();
|
||||
|
||||
public static synchronized void addSnake(Snake snake) {
|
||||
if (snakes.isEmpty()) {
|
||||
startTimer();
|
||||
private static Timer gameTimer = null;
|
||||
|
||||
public static void addSnake(Snake snake) {
|
||||
synchronized (MONITOR) {
|
||||
if (snakes.isEmpty()) {
|
||||
startTimer();
|
||||
}
|
||||
snakes.put(Integer.valueOf(snake.getId()), snake);
|
||||
}
|
||||
snakes.put(Integer.valueOf(snake.getId()), snake);
|
||||
}
|
||||
|
||||
public static Collection<Snake> getSnakes() {
|
||||
return Collections.unmodifiableCollection(snakes.values());
|
||||
}
|
||||
|
||||
public static synchronized void removeSnake(Snake snake) {
|
||||
snakes.remove(Integer.valueOf(snake.getId()));
|
||||
if (snakes.isEmpty()) {
|
||||
stopTimer();
|
||||
public static void removeSnake(Snake snake) {
|
||||
synchronized (MONITOR) {
|
||||
snakes.remove(Integer.valueOf(snake.getId()));
|
||||
if (snakes.isEmpty()) {
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user