From 92bb24e365b700685e4a078ee6bb02cd4b2cda96 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 1 Jul 2016 10:00:29 +0100 Subject: [PATCH] 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 --- .../audit/InMemoryAuditEventRepository.java | 32 +++-- .../health/MailHealthIndicatorTests.java | 4 +- .../boot/cli/command/run/RunCommand.java | 51 +++---- .../command/run/SpringApplicationRunner.java | 97 +++++++------ .../boot/devtools/livereload/Connection.java | 4 +- .../devtools/livereload/LiveReloadServer.java | 92 +++++++------ .../boot/devtools/restart/Restarter.java | 59 +++++--- .../tunnel/client/HttpTunnelConnection.java | 2 +- .../devtools/tunnel/client/TunnelClient.java | 63 +++++---- .../tunnel/client/TunnelClientListeners.java | 6 +- .../payload/HttpTunnelPayloadForwarder.java | 36 ++--- .../samples/websocket/jetty/snake/Snake.java | 127 ++++++++++-------- .../websocket/jetty/snake/SnakeTimer.java | 30 +++-- .../samples/websocket/tomcat/snake/Snake.java | 127 ++++++++++-------- .../websocket/tomcat/snake/SnakeTimer.java | 30 +++-- .../websocket/undertow/snake/Snake.java | 127 ++++++++++-------- .../websocket/undertow/snake/SnakeTimer.java | 30 +++-- .../MockitoAopProxyTargetInterceptor.java | 34 +++-- .../boot/maven/AbstractRunMojo.java | 17 ++- .../jetty/JettyEmbeddedServletContainer.java | 56 ++++---- .../TomcatEmbeddedServletContainer.java | 90 +++++++------ .../UndertowEmbeddedServletContainer.java | 72 +++++----- .../PoolingConnectionFactoryBean.java | 4 +- .../jta/bitronix/PoolingDataSourceBean.java | 4 +- .../atomikos/AtomikosDataSourceBeanTests.java | 2 +- 25 files changed, 685 insertions(+), 511 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java index 96dc0e5f9a..ae76eb43ad 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java @@ -33,6 +33,8 @@ public class InMemoryAuditEventRepository implements AuditEventRepository { private static final int DEFAULT_CAPACITY = 4000; + private final Object monitor = new Object(); + /** * Circular buffer of the event with tail pointing to the last element. */ @@ -52,34 +54,40 @@ public class InMemoryAuditEventRepository implements AuditEventRepository { * Set the capacity of this event repository. * @param capacity the capacity */ - public synchronized void setCapacity(int capacity) { - this.events = new AuditEvent[capacity]; + public void setCapacity(int capacity) { + synchronized (this.monitor) { + this.events = new AuditEvent[capacity]; + } } @Override - public synchronized void add(AuditEvent event) { + public void add(AuditEvent event) { Assert.notNull(event, "AuditEvent must not be null"); - this.tail = (this.tail + 1) % this.events.length; - this.events[this.tail] = event; + synchronized (this.monitor) { + this.tail = (this.tail + 1) % this.events.length; + this.events[this.tail] = event; + } } @Override - public synchronized List find(Date after) { + public List find(Date after) { return find(null, after, null); } @Override - public synchronized List find(String principal, Date after) { + public List find(String principal, Date after) { return find(principal, after, null); } @Override - public synchronized List find(String principal, Date after, String type) { + public List find(String principal, Date after, String type) { LinkedList events = new LinkedList(); - for (int i = 0; i < this.events.length; i++) { - AuditEvent event = resolveTailEvent(i); - if (event != null && isMatch(principal, after, type, event)) { - events.addFirst(event); + synchronized (this.events) { + for (int i = 0; i < this.events.length; i++) { + AuditEvent event = resolveTailEvent(i); + if (event != null && isMatch(principal, after, type, event)) { + events.addFirst(event); + } } } return events; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MailHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MailHealthIndicatorTests.java index be7d7fe6e5..9ee62c4ed1 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MailHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MailHealthIndicatorTests.java @@ -88,8 +88,8 @@ public class MailHealthIndicatorTests { } @Override - public synchronized void connect(String host, int port, String user, - String password) throws MessagingException { + public void connect(String host, int port, String user, String password) + throws MessagingException { } @Override diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java index c6b00ef112..7878b6293d 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java @@ -61,6 +61,8 @@ public class RunCommand extends OptionParsingCommand { private static class RunOptionHandler extends CompilerOptionHandler { + private final Object monitor = new Object(); + private OptionSpec watchOption; private OptionSpec verboseOption; @@ -77,36 +79,39 @@ public class RunCommand extends OptionParsingCommand { this.quietOption = option(Arrays.asList("quiet", "q"), "Quiet logging"); } - public synchronized void stop() { - if (this.runner != null) { - this.runner.stop(); + public void stop() { + synchronized (this.monitor) { + if (this.runner != null) { + this.runner.stop(); + } + this.runner = null; } - this.runner = null; } @Override protected synchronized ExitStatus run(OptionSet options) throws Exception { + synchronized (this.monitor) { + if (this.runner != null) { + throw new RuntimeException( + "Already running. Please stop the current application before running another (use the 'stop' command)."); + } - if (this.runner != null) { - throw new RuntimeException( - "Already running. Please stop the current application before running another (use the 'stop' command)."); + SourceOptions sourceOptions = new SourceOptions(options); + + List repositoryConfiguration = RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + repositoryConfiguration.add(0, new RepositoryConfiguration("local", + new File("repository").toURI(), true)); + + SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter( + options, this, repositoryConfiguration); + + this.runner = new SpringApplicationRunner(configuration, + sourceOptions.getSourcesArray(), sourceOptions.getArgsArray()); + this.runner.compileAndRun(); + + return ExitStatus.OK; } - - SourceOptions sourceOptions = new SourceOptions(options); - - List repositoryConfiguration = RepositoryConfigurationFactory - .createDefaultRepositoryConfiguration(); - repositoryConfiguration.add(0, new RepositoryConfiguration("local", - new File("repository").toURI(), true)); - - SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter( - options, this, repositoryConfiguration); - - this.runner = new SpringApplicationRunner(configuration, - sourceOptions.getSourcesArray(), sourceOptions.getArgsArray()); - this.runner.compileAndRun(); - - return ExitStatus.OK; } /** diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java index bc66001b03..0002e527f4 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,8 @@ public class SpringApplicationRunner { private static int runnerCounter = 0; + private final Object monitor = new Object(); + private final SpringApplicationRunnerConfiguration configuration; private final String[] sources; @@ -84,34 +86,38 @@ public class SpringApplicationRunner { } /** - * Compile and run the application. This method is synchronized as it can be called by - * file monitoring threads. + * Compile and run the application. + * * @throws Exception on error */ - public synchronized void compileAndRun() throws Exception { - try { - stop(); - Object[] compiledSources = compile(); - monitorForChanges(); - // Run in new thread to ensure that the context classloader is setup - this.runThread = new RunThread(compiledSources); - this.runThread.start(); - this.runThread.join(); - } - catch (Exception ex) { - if (this.fileWatchThread == null) { - throw ex; + public void compileAndRun() throws Exception { + synchronized (this.monitor) { + try { + stop(); + Object[] compiledSources = compile(); + monitorForChanges(); + // Run in new thread to ensure that the context classloader is setup + this.runThread = new RunThread(compiledSources); + this.runThread.start(); + this.runThread.join(); } - else { - ex.printStackTrace(); + catch (Exception ex) { + if (this.fileWatchThread == null) { + throw ex; + } + else { + ex.printStackTrace(); + } } } } public void stop() { - if (this.runThread != null) { - this.runThread.shutdown(); - this.runThread = null; + synchronized (this.monitor) { + if (this.runThread != null) { + this.runThread.shutdown(); + this.runThread = null; + } } } @@ -136,6 +142,8 @@ public class SpringApplicationRunner { */ private class RunThread extends Thread { + private final Object monitor = new Object(); + private final Object[] compiledSources; private Object applicationContext; @@ -155,33 +163,38 @@ public class SpringApplicationRunner { @Override public void run() { - try { - this.applicationContext = new SpringApplicationLauncher( - getContextClassLoader()).launch(this.compiledSources, - SpringApplicationRunner.this.args); - } - catch (Exception ex) { - ex.printStackTrace(); + synchronized (this.monitor) { + try { + this.applicationContext = new SpringApplicationLauncher( + getContextClassLoader()).launch(this.compiledSources, + SpringApplicationRunner.this.args); + } + catch (Exception ex) { + ex.printStackTrace(); + } } } /** * Shutdown the thread, closing any previously opened application context. */ - public synchronized void shutdown() { - if (this.applicationContext != null) { - try { - Method method = this.applicationContext.getClass().getMethod("close"); - method.invoke(this.applicationContext); - } - catch (NoSuchMethodException ex) { - // Not an application context that we can close - } - catch (Exception ex) { - ex.printStackTrace(); - } - finally { - this.applicationContext = null; + public void shutdown() { + synchronized (this.monitor) { + if (this.applicationContext != null) { + try { + Method method = this.applicationContext.getClass() + .getMethod("close"); + method.invoke(this.applicationContext); + } + catch (NoSuchMethodException ex) { + // Not an application context that we can close + } + catch (Exception ex) { + ex.printStackTrace(); + } + finally { + this.applicationContext = null; + } } } } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java index 6b6b6371ea..f890124e0d 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,7 +139,7 @@ class Connection { } } - private synchronized void writeWebSocketFrame(Frame frame) throws IOException { + private void writeWebSocketFrame(Frame frame) throws IOException { frame.write(this.outputStream); } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/LiveReloadServer.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/LiveReloadServer.java index 922fa15451..65b2676e18 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/LiveReloadServer.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/LiveReloadServer.java @@ -53,6 +53,13 @@ public class LiveReloadServer { private static final int READ_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(4); + private final ExecutorService executor = Executors + .newCachedThreadPool(new WorkerThreadFactory()); + + private final List connections = new ArrayList(); + + private final Object monitor = new Object(); + private final int port; private final ThreadFactory threadFactory; @@ -61,11 +68,6 @@ public class LiveReloadServer { private Thread listenThread; - private ExecutorService executor = Executors - .newCachedThreadPool(new WorkerThreadFactory()); - - private List connections = new ArrayList(); - /** * Create a new {@link LiveReloadServer} listening on the default port. */ @@ -112,29 +114,33 @@ public class LiveReloadServer { * Start the livereload server and accept incoming connections. * @throws IOException in case of I/O errors */ - public synchronized void start() throws IOException { - Assert.state(!isStarted(), "Server already started"); - logger.debug("Starting live reload server on port " + this.port); - this.serverSocket = new ServerSocket(this.port); - this.listenThread = this.threadFactory.newThread(new Runnable() { + public void start() throws IOException { + synchronized (this.monitor) { + Assert.state(!isStarted(), "Server already started"); + logger.debug("Starting live reload server on port " + this.port); + this.serverSocket = new ServerSocket(this.port); + this.listenThread = this.threadFactory.newThread(new Runnable() { - @Override - public void run() { - acceptConnections(); - } + @Override + public void run() { + acceptConnections(); + } - }); - this.listenThread.setDaemon(true); - this.listenThread.setName("Live Reload Server"); - this.listenThread.start(); + }); + this.listenThread.setDaemon(true); + this.listenThread.setName("Live Reload Server"); + this.listenThread.start(); + } } /** * Return if the server has been started. * @return {@code true} if the server is running */ - public synchronized boolean isStarted() { - return this.listenThread != null; + public boolean isStarted() { + synchronized (this.monitor) { + return this.listenThread != null; + } } /** @@ -168,30 +174,32 @@ public class LiveReloadServer { * Gracefully stop the livereload server. * @throws IOException in case of I/O errors */ - public synchronized void stop() throws IOException { - if (this.listenThread != null) { - closeAllConnections(); - try { - this.executor.shutdown(); - this.executor.awaitTermination(1, TimeUnit.MINUTES); + public void stop() throws IOException { + synchronized (this.monitor) { + if (this.listenThread != null) { + closeAllConnections(); + try { + this.executor.shutdown(); + this.executor.awaitTermination(1, TimeUnit.MINUTES); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + this.serverSocket.close(); + try { + this.listenThread.join(); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + this.listenThread = null; + this.serverSocket = null; } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - this.serverSocket.close(); - try { - this.listenThread.join(); - } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - this.listenThread = null; - this.serverSocket = null; } } private void closeAllConnections() throws IOException { - synchronized (this.connections) { + synchronized (this.monitor) { for (Connection connection : this.connections) { connection.close(); } @@ -202,7 +210,7 @@ public class LiveReloadServer { * Trigger livereload of all connected clients. */ public void triggerReload() { - synchronized (this.connections) { + synchronized (this.monitor) { for (Connection connection : this.connections) { try { connection.triggerReload(); @@ -215,13 +223,13 @@ public class LiveReloadServer { } private void addConnection(Connection connection) { - synchronized (this.connections) { + synchronized (this.monitor) { this.connections.add(connection); } } private void removeConnection(Connection connection) { - synchronized (this.connections) { + synchronized (this.monitor) { this.connections.remove(connection); } } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 80c70d0192..1c94bc5506 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -80,10 +80,24 @@ import org.springframework.util.ReflectionUtils; */ public class Restarter { + private static final Object INSTANCE_MONITOR = new Object(); + private static final String[] NO_ARGS = {}; private static Restarter instance; + private final Set urls = new LinkedHashSet(); + + private final ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles(); + + private final Map attributes = new HashMap(); + + private final BlockingDeque leakSafeThreads = new LinkedBlockingDeque(); + + private final Lock stopLock = new ReentrantLock(); + + private final Object monitor = new Object(); + private Log logger = new DeferredLog(); private final boolean forceReferenceCleanup; @@ -100,18 +114,8 @@ public class Restarter { private final UncaughtExceptionHandler exceptionHandler; - private final Set urls = new LinkedHashSet(); - - private final ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles(); - - private final Map attributes = new HashMap(); - - private final BlockingDeque leakSafeThreads = new LinkedBlockingDeque(); - private boolean finished = false; - private final Lock stopLock = new ReentrantLock(); - private volatile ConfigurableApplicationContext rootContext; /** @@ -394,15 +398,20 @@ public class Restarter { * Called to finish {@link Restarter} initialization when application logging is * available. */ - synchronized void finish() { - if (!isFinished()) { - this.logger = DeferredLog.replay(this.logger, LogFactory.getLog(getClass())); - this.finished = true; + void finish() { + synchronized (this.monitor) { + if (!isFinished()) { + this.logger = DeferredLog.replay(this.logger, + LogFactory.getLog(getClass())); + this.finished = true; + } } } - synchronized boolean isFinished() { - return this.finished; + boolean isFinished() { + synchronized (this.monitor) { + return this.finished; + } } void prepare(ConfigurableApplicationContext applicationContext) { @@ -514,7 +523,7 @@ public class Restarter { public static void initialize(String[] args, boolean forceReferenceCleanup, RestartInitializer initializer, boolean restartOnInitialize) { Restarter localInstance = null; - synchronized (Restarter.class) { + synchronized (INSTANCE_MONITOR) { if (instance == null) { localInstance = new Restarter(Thread.currentThread(), args, forceReferenceCleanup, initializer); @@ -531,9 +540,11 @@ public class Restarter { * {@link #initialize(String[]) initialization}. * @return the restarter */ - public synchronized static Restarter getInstance() { - Assert.state(instance != null, "Restarter has not been initialized"); - return instance; + public static Restarter getInstance() { + synchronized (INSTANCE_MONITOR) { + Assert.state(instance != null, "Restarter has not been initialized"); + return instance; + } } /** @@ -541,7 +552,9 @@ public class Restarter { * @param instance the instance to set */ final static void setInstance(Restarter instance) { - Restarter.instance = instance; + synchronized (INSTANCE_MONITOR) { + Restarter.instance = instance; + } } /** @@ -549,7 +562,9 @@ public class Restarter { * application code. */ public static void clearInstance() { - instance = null; + synchronized (INSTANCE_MONITOR) { + instance = null; + } } /** diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/HttpTunnelConnection.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/HttpTunnelConnection.java index 9516bf980e..9014fb757c 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/HttpTunnelConnection.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/HttpTunnelConnection.java @@ -150,7 +150,7 @@ public class HttpTunnelConnection implements TunnelConnection { return size; } - private synchronized void openNewConnection(final HttpTunnelPayload payload) { + private void openNewConnection(final HttpTunnelPayload payload) { HttpTunnelConnection.this.executor.execute(new Runnable() { @Override diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClient.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClient.java index 37cf021e0e..7847c7717b 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClient.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClient.java @@ -46,12 +46,14 @@ public class TunnelClient implements SmartInitializingSingleton { private static final Log logger = LogFactory.getLog(TunnelClient.class); + private final TunnelClientListeners listeners = new TunnelClientListeners(); + + private final Object monitor = new Object(); + private final int listenPort; private final TunnelConnection tunnelConnection; - private TunnelClientListeners listeners = new TunnelClientListeners(); - private ServerThread serverThread; public TunnelClient(int listenPort, TunnelConnection tunnelConnection) { @@ -63,12 +65,14 @@ public class TunnelClient implements SmartInitializingSingleton { @Override public void afterSingletonsInstantiated() { - if (this.serverThread == null) { - try { - start(); - } - catch (IOException ex) { - throw new IllegalStateException(ex); + synchronized (this.monitor) { + if (this.serverThread == null) { + try { + start(); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } } } } @@ -77,35 +81,42 @@ public class TunnelClient implements SmartInitializingSingleton { * Start the client and accept incoming connections on the port. * @throws IOException in case of I/O errors */ - public synchronized void start() throws IOException { - Assert.state(this.serverThread == null, "Server already started"); - ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); - serverSocketChannel.socket().bind(new InetSocketAddress(this.listenPort)); - logger.trace("Listening for TCP traffic to tunnel on port " + this.listenPort); - this.serverThread = new ServerThread(serverSocketChannel); - this.serverThread.start(); + public void start() throws IOException { + synchronized (this.monitor) { + Assert.state(this.serverThread == null, "Server already started"); + ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); + serverSocketChannel.socket().bind(new InetSocketAddress(this.listenPort)); + logger.trace( + "Listening for TCP traffic to tunnel on port " + this.listenPort); + this.serverThread = new ServerThread(serverSocketChannel); + this.serverThread.start(); + } } /** * Stop the client, disconnecting any servers. * @throws IOException in case of I/O errors */ - public synchronized void stop() throws IOException { - if (this.serverThread != null) { - logger.trace("Closing tunnel client on port " + this.listenPort); - this.serverThread.close(); - try { - this.serverThread.join(2000); + public void stop() throws IOException { + synchronized (this.monitor) { + if (this.serverThread != null) { + logger.trace("Closing tunnel client on port " + this.listenPort); + this.serverThread.close(); + try { + this.serverThread.join(2000); + } + catch (InterruptedException ex) { + // Ignore + } + this.serverThread = null; } - catch (InterruptedException ex) { - // Ignore - } - this.serverThread = null; } } protected final ServerThread getServerThread() { - return this.serverThread; + synchronized (this.monitor) { + return this.serverThread; + } } public void addListener(TunnelClientListener listener) { diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClientListeners.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClientListeners.java index c926ced884..0cd704df14 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClientListeners.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/client/TunnelClientListeners.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.boot.devtools.tunnel.client; import java.nio.channels.SocketChannel; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.springframework.util.Assert; @@ -29,7 +29,7 @@ import org.springframework.util.Assert; */ class TunnelClientListeners { - private final List listeners = new ArrayList(); + private final List listeners = new CopyOnWriteArrayList(); public void addListener(TunnelClientListener listener) { Assert.notNull(listener, "Listener must not be null"); diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/payload/HttpTunnelPayloadForwarder.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/payload/HttpTunnelPayloadForwarder.java index 516d8dde71..942bed1237 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/payload/HttpTunnelPayloadForwarder.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/tunnel/payload/HttpTunnelPayloadForwarder.java @@ -34,12 +34,14 @@ public class HttpTunnelPayloadForwarder { private static final int MAXIMUM_QUEUE_SIZE = 100; + private final Map queue = new HashMap(); + + private final Object monitor = new Object(); + private final WritableByteChannel targetChannel; private long lastRequestSeq = 0; - private final Map queue = new HashMap(); - /** * Create a new {@link HttpTunnelPayloadForwarder} instance. * @param targetChannel the target channel @@ -49,20 +51,22 @@ public class HttpTunnelPayloadForwarder { this.targetChannel = targetChannel; } - public synchronized void forward(HttpTunnelPayload payload) throws IOException { - long seq = payload.getSequence(); - if (this.lastRequestSeq != seq - 1) { - Assert.state(this.queue.size() < MAXIMUM_QUEUE_SIZE, - "Too many messages queued"); - this.queue.put(seq, payload); - return; - } - payload.logOutgoing(); - payload.writeTo(this.targetChannel); - this.lastRequestSeq = seq; - HttpTunnelPayload queuedItem = this.queue.get(seq + 1); - if (queuedItem != null) { - forward(queuedItem); + public void forward(HttpTunnelPayload payload) throws IOException { + synchronized (this.monitor) { + long seq = payload.getSequence(); + if (this.lastRequestSeq != seq - 1) { + Assert.state(this.queue.size() < MAXIMUM_QUEUE_SIZE, + "Too many messages queued"); + this.queue.put(seq, payload); + return; + } + payload.logOutgoing(); + payload.writeTo(this.targetChannel); + this.lastRequestSeq = seq; + HttpTunnelPayload queuedItem = this.queue.get(seq + 1); + if (queuedItem != null) { + forward(queuedItem); + } } } diff --git a/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/Snake.java b/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/Snake.java index 28d941685f..a819650835 100644 --- a/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/Snake.java +++ b/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/Snake.java @@ -28,15 +28,22 @@ public class Snake { private static final int DEFAULT_LENGTH = 5; + private final Deque tail = new ArrayDeque(); + + 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 tail = new ArrayDeque(); 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 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 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 snakes) throws Exception { @@ -104,29 +117,37 @@ public class Snake { } } - public synchronized Location getHead() { - return this.head; - } - - public synchronized Collection 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 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() { diff --git a/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/SnakeTimer.java b/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/SnakeTimer.java index 264fe4da11..d78a40ff9e 100644 --- a/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/SnakeTimer.java +++ b/spring-boot-samples/spring-boot-sample-websocket-jetty/src/main/java/samples/websocket/jetty/snake/SnakeTimer.java @@ -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 snakes = new ConcurrentHashMap(); - 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 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(); + } } } diff --git a/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/Snake.java b/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/Snake.java index 1b130370ad..ce0eec69d8 100644 --- a/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/Snake.java +++ b/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/Snake.java @@ -28,15 +28,22 @@ public class Snake { private static final int DEFAULT_LENGTH = 5; + private final Deque tail = new ArrayDeque(); + + 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 tail = new ArrayDeque(); 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 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 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 snakes) throws Exception { @@ -104,29 +117,37 @@ public class Snake { } } - public synchronized Location getHead() { - return this.head; - } - - public synchronized Collection 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 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() { diff --git a/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/SnakeTimer.java b/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/SnakeTimer.java index 4ef4609b10..07f5ecc558 100644 --- a/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/SnakeTimer.java +++ b/spring-boot-samples/spring-boot-sample-websocket-tomcat/src/main/java/samples/websocket/tomcat/snake/SnakeTimer.java @@ -33,29 +33,35 @@ import org.apache.juli.logging.LogFactory; */ public class SnakeTimer { - private static final Log log = LogFactory.getLog(SnakeTimer.class); - - private static Timer gameTimer = null; - private static final long TICK_DELAY = 100; + private static final Object MONITOR = new Object(); + + private static final Log log = LogFactory.getLog(SnakeTimer.class); + private static final ConcurrentHashMap snakes = new ConcurrentHashMap(); - 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 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(); + } } } diff --git a/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/Snake.java b/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/Snake.java index 583a0d2308..ba95f12688 100644 --- a/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/Snake.java +++ b/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/Snake.java @@ -28,15 +28,22 @@ public class Snake { private static final int DEFAULT_LENGTH = 5; + private final Deque tail = new ArrayDeque(); + + 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 tail = new ArrayDeque(); 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 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 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 snakes) throws Exception { @@ -104,29 +117,37 @@ public class Snake { } } - public synchronized Location getHead() { - return this.head; - } - - public synchronized Collection 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 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() { diff --git a/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/SnakeTimer.java b/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/SnakeTimer.java index 54126012fc..3f76cbe03a 100644 --- a/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/SnakeTimer.java +++ b/spring-boot-samples/spring-boot-sample-websocket-undertow/src/main/java/samples/websocket/undertow/snake/SnakeTimer.java @@ -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 snakes = new ConcurrentHashMap(); - 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 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(); + } } } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java index 58c5c2c6f1..48748ad1d0 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java @@ -89,6 +89,8 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { private static class Verification { + private final Object monitor = new Object(); + private final MockingProgress progress; Verification(Object target) { @@ -101,25 +103,29 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { this.progress = (MockingProgress) ReflectionUtils.getField(field, container); } - public synchronized boolean isVerifying() { - VerificationMode mode = this.progress.pullVerificationMode(); - if (mode != null) { - this.progress.verificationStarted(mode); - return true; + public boolean isVerifying() { + synchronized (this.monitor) { + VerificationMode mode = this.progress.pullVerificationMode(); + if (mode != null) { + this.progress.verificationStarted(mode); + return true; + } + return false; } - return false; } - public synchronized void replaceVerifyMock(Object source, Object target) { - VerificationMode mode = this.progress.pullVerificationMode(); - if (mode != null) { - if (mode instanceof MockAwareVerificationMode) { - MockAwareVerificationMode mockAwareMode = (MockAwareVerificationMode) mode; - if (mockAwareMode.getMock() == source) { - mode = new MockAwareVerificationMode(target, mockAwareMode); + public void replaceVerifyMock(Object source, Object target) { + synchronized (this.monitor) { + VerificationMode mode = this.progress.pullVerificationMode(); + if (mode != null) { + if (mode instanceof MockAwareVerificationMode) { + MockAwareVerificationMode mockAwareMode = (MockAwareVerificationMode) mode; + if (mockAwareMode.getMock() == source) { + mode = new MockAwareVerificationMode(target, mockAwareMode); + } } + this.progress.verificationStarted(mode); } - this.progress.verificationStarted(mode); } } diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java index 06af32227b..806078e7a7 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java @@ -426,6 +426,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { */ class IsolatedThreadGroup extends ThreadGroup { + private final Object monitor = new Object(); + private Throwable exception; IsolatedThreadGroup(String name) { @@ -435,18 +437,21 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { @Override public void uncaughtException(Thread thread, Throwable ex) { if (!(ex instanceof ThreadDeath)) { - synchronized (this) { + synchronized (this.monitor) { this.exception = (this.exception == null ? ex : this.exception); } getLog().warn(ex); } } - public synchronized void rethrowUncaughtException() - throws MojoExecutionException { - if (this.exception != null) { - throw new MojoExecutionException("An exception occurred while running. " - + this.exception.getMessage(), this.exception); + public void rethrowUncaughtException() throws MojoExecutionException { + synchronized (this.monitor) { + if (this.exception != null) { + throw new MojoExecutionException( + "An exception occurred while running. " + + this.exception.getMessage(), + this.exception); + } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainer.java index e60cbea214..815f82be77 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainer.java @@ -51,6 +51,8 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer { private static final Log logger = LogFactory .getLog(JettyEmbeddedServletContainer.class); + private final Object monitor = new Object(); + private final Server server; private final boolean autoStart; @@ -77,22 +79,24 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer { initialize(); } - private synchronized void initialize() { - try { - // Cache and clear the connectors to prevent requests being handled before - // the application context is ready - this.connectors = this.server.getConnectors(); - this.server.setConnectors(null); + private void initialize() { + synchronized (this.monitor) { + try { + // Cache and clear the connectors to prevent requests being handled before + // the application context is ready + this.connectors = this.server.getConnectors(); + this.server.setConnectors(null); - // Start the server so that the ServletContext is available - this.server.start(); - this.server.setStopAtShutdown(false); - } - catch (Exception ex) { - // Ensure process isn't left running - stopSilently(); - throw new EmbeddedServletContainerException( - "Unable to start embedded Jetty servlet container", ex); + // Start the server so that the ServletContext is available + this.server.start(); + this.server.setStopAtShutdown(false); + } + catch (Exception ex) { + // Ensure process isn't left running + stopSilently(); + throw new EmbeddedServletContainerException( + "Unable to start embedded Jetty servlet container", ex); + } } } @@ -191,16 +195,18 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer { } @Override - public synchronized void stop() { - try { - this.server.stop(); - } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - catch (Exception ex) { - throw new EmbeddedServletContainerException( - "Unable to stop embedded Jetty servlet container", ex); + public void stop() { + synchronized (this.monitor) { + try { + this.server.stop(); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + catch (Exception ex) { + throw new EmbeddedServletContainerException( + "Unable to stop embedded Jetty servlet container", ex); + } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java index 3da8455723..210b921771 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java @@ -53,12 +53,14 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer private static final Log logger = LogFactory .getLog(TomcatEmbeddedServletContainer.class); - private static AtomicInteger containerCounter = new AtomicInteger(-1); + private static final AtomicInteger containerCounter = new AtomicInteger(-1); - private final Tomcat tomcat; + private final Object monitor = new Object(); private final Map serviceConnectors = new HashMap(); + private final Tomcat tomcat; + private final boolean autoStart; /** @@ -81,37 +83,39 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer initialize(); } - private synchronized void initialize() throws EmbeddedServletContainerException { + private void initialize() throws EmbeddedServletContainerException { TomcatEmbeddedServletContainer.logger .info("Tomcat initialized with port(s): " + getPortsDescription(false)); - try { - addInstanceIdToEngineName(); - - // Remove service connectors to that protocol binding doesn't happen yet - removeServiceConnectors(); - - // Start the server to trigger initialization listeners - this.tomcat.start(); - - // We can re-throw failure exception directly in the main thread - rethrowDeferredStartupExceptions(); - - Context context = findContext(); + synchronized (this.monitor) { try { - ContextBindings.bindClassLoader(context, getNamingToken(context), - getClass().getClassLoader()); - } - catch (NamingException ex) { - // Naming is not enabled. Continue - } + addInstanceIdToEngineName(); - // Unlike Jetty, all Tomcat threads are daemon threads. We create a - // blocking non-daemon to stop immediate shutdown - startDaemonAwaitThread(); - } - catch (Exception ex) { - throw new EmbeddedServletContainerException("Unable to start embedded Tomcat", - ex); + // Remove service connectors to that protocol binding doesn't happen yet + removeServiceConnectors(); + + // Start the server to trigger initialization listeners + this.tomcat.start(); + + // We can re-throw failure exception directly in the main thread + rethrowDeferredStartupExceptions(); + + Context context = findContext(); + try { + ContextBindings.bindClassLoader(context, getNamingToken(context), + getClass().getClassLoader()); + } + catch (NamingException ex) { + // Naming is not enabled. Continue + } + + // Unlike Jetty, all Tomcat threads are daemon threads. We create a + // blocking non-daemon to stop immediate shutdown + startDaemonAwaitThread(); + } + catch (Exception ex) { + throw new EmbeddedServletContainerException( + "Unable to start embedded Tomcat", ex); + } } } @@ -266,22 +270,24 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer } @Override - public synchronized void stop() throws EmbeddedServletContainerException { - try { + public void stop() throws EmbeddedServletContainerException { + synchronized (this.monitor) { try { - stopTomcat(); - this.tomcat.destroy(); + try { + stopTomcat(); + this.tomcat.destroy(); + } + catch (LifecycleException ex) { + // swallow and continue + } } - catch (LifecycleException ex) { - // swallow and continue + catch (Exception ex) { + throw new EmbeddedServletContainerException( + "Unable to stop embedded Tomcat", ex); + } + finally { + containerCounter.decrementAndGet(); } - } - catch (Exception ex) { - throw new EmbeddedServletContainerException("Unable to stop embedded Tomcat", - ex); - } - finally { - containerCounter.decrementAndGet(); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java index 9930a95060..c84d391849 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -70,6 +70,8 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine private static final Log logger = LogFactory .getLog(UndertowEmbeddedServletContainer.class); + private final Object monitor = new Object(); + private final Builder builder; private final DeploymentManager manager; @@ -197,31 +199,33 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine } @Override - public synchronized void start() throws EmbeddedServletContainerException { - try { - if (!this.autoStart) { - return; - } - if (this.undertow == null) { - this.undertow = createUndertowServer(); - } - this.undertow.start(); - this.started = true; - UndertowEmbeddedServletContainer.logger - .info("Undertow started on port(s) " + getPortsDescription()); - } - catch (Exception ex) { - if (findBindException(ex) != null) { - List failedPorts = getConfiguredPorts(); - List actualPorts = getActualPorts(); - failedPorts.removeAll(actualPorts); - if (failedPorts.size() == 1) { - throw new PortInUseException( - failedPorts.iterator().next().getNumber()); + public void start() throws EmbeddedServletContainerException { + synchronized (this.monitor) { + try { + if (!this.autoStart) { + return; } + if (this.undertow == null) { + this.undertow = createUndertowServer(); + } + this.undertow.start(); + this.started = true; + UndertowEmbeddedServletContainer.logger + .info("Undertow started on port(s) " + getPortsDescription()); + } + catch (Exception ex) { + if (findBindException(ex) != null) { + List failedPorts = getConfiguredPorts(); + List actualPorts = getActualPorts(); + failedPorts.removeAll(actualPorts); + if (failedPorts.size() == 1) { + throw new PortInUseException( + failedPorts.iterator().next().getNumber()); + } + } + throw new EmbeddedServletContainerException( + "Unable to start embedded Undertow", ex); } - throw new EmbeddedServletContainerException( - "Unable to start embedded Undertow", ex); } } @@ -356,16 +360,18 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine } @Override - public synchronized void stop() throws EmbeddedServletContainerException { - if (this.started) { - try { - this.started = false; - this.manager.stop(); - this.undertow.stop(); - } - catch (Exception ex) { - throw new EmbeddedServletContainerException("Unable to stop undertow", - ex); + public void stop() throws EmbeddedServletContainerException { + synchronized (this.monitor) { + if (this.started) { + try { + this.started = false; + this.manager.stop(); + this.undertow.stop(); + } + catch (Exception ex) { + throw new EmbeddedServletContainerException("Unable to stop undertow", + ex); + } } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBean.java index c7a8aa0014..8f0ae5fecc 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ import org.springframework.util.StringUtils; public class PoolingConnectionFactoryBean extends PoolingConnectionFactory implements BeanNameAware, InitializingBean, DisposableBean { - private static ThreadLocal source = new ThreadLocal(); + private static final ThreadLocal source = new ThreadLocal(); private String beanName; diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java index 2456605dfb..449298bdc2 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ import org.springframework.util.StringUtils; public class PoolingDataSourceBean extends PoolingDataSource implements BeanNameAware, InitializingBean { - private static ThreadLocal source = new ThreadLocal(); + private static final ThreadLocal source = new ThreadLocal(); private XADataSource dataSource; diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java index 98b58fb6d3..440ea5a3d4 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java @@ -51,7 +51,7 @@ public class AtomikosDataSourceBeanTests { } @Override - public synchronized void close() { + public void close() { } }