| /******************************************************************************* |
| * Copyright (c) 2003, 2014 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.tests.runtime.jobs; |
| |
| import java.util.*; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.jobs.ILock; |
| |
| /** |
| * Monitor ensuring no more than one writer working concurrently. |
| * Multiple readers are allowed to perform simultaneously. |
| */ |
| public class ReadWriteMonitor { |
| private final Set<Thread> blockedThreads = Collections.synchronizedSet(new HashSet<Thread>()); |
| private ILock lock; |
| /** |
| * <0 : writing (cannot go beyond -1, i.e one concurrent writer) |
| * =0 : idle |
| * >0 : reading (number of concurrent readers) |
| */ |
| private int status = 0; |
| |
| public ReadWriteMonitor(ILock lock) { |
| this.lock = lock; |
| } |
| |
| /** |
| * Concurrent reading is allowed |
| * Blocking only when already writing. |
| */ |
| public void enterRead() { |
| if (!incrementRead()) { |
| blockedThreads.add(Thread.currentThread()); |
| //wait until this thread or another reader acquires the lock |
| while (!incrementRead()) { |
| try { |
| lock.acquire(Long.MAX_VALUE); |
| setStatus(1); |
| break; |
| } catch (InterruptedException e) { |
| } |
| } |
| blockedThreads.remove(Thread.currentThread()); |
| } |
| //interrupt other threads so all readers can proceed |
| interruptBlockedThreads(); |
| } |
| |
| /** |
| * Only one writer at a time is allowed to perform |
| * Blocking only when already writing or reading. |
| */ |
| public void enterWrite() { |
| blockedThreads.add(Thread.currentThread()); |
| while (true) { |
| try { |
| lock.acquire(Long.MAX_VALUE); |
| setStatus(-1); |
| break; |
| } catch (InterruptedException e) { |
| } |
| } |
| blockedThreads.remove(Thread.currentThread()); |
| } |
| |
| /** |
| * Synchronized to ensure field value is reconciled. |
| */ |
| private synchronized void setStatus(int s) { |
| this.status = s; |
| } |
| |
| /** |
| * Only notify waiting writer(s) if last reader |
| */ |
| public synchronized void exitRead() { |
| Assert.isTrue(status > 0, "exitRead without enterRead"); |
| if (--status == 0) { |
| lock.release(); |
| interruptBlockedThreads(); |
| } |
| } |
| |
| /** |
| * When writing is over, all readers and possible |
| * writers are granted permission to restart concurrently |
| */ |
| public synchronized void exitWrite() { |
| Assert.isTrue(status == -1, "exitWrite without enterWrite"); |
| status = 0; |
| lock.release(); |
| interruptBlockedThreads(); |
| } |
| |
| /** |
| * Atomic exitWrite/enterRead: Allows to keep monitor in between |
| * exit write and next enter read. |
| * When writing is over, all readers are granted permissing to restart |
| * concurrently. |
| * This is the same as: |
| * <pre> |
| * synchronized(monitor) { |
| * monitor.exitWrite(); |
| * monitor.enterRead(); |
| * } |
| * </pre> |
| */ |
| public synchronized void exitWriteEnterRead() { |
| //don't release the lock, just fix the counter to indicate a single reader |
| status = 1; |
| interruptBlockedThreads(); |
| } |
| |
| /** |
| * Increment the reader count if it is safe to do so. |
| */ |
| private synchronized boolean incrementRead() { |
| if (status <= 0) |
| return false; |
| status++; |
| return true; |
| } |
| |
| /** |
| * Wake up all waiting threads so they can compete for the counter again. |
| */ |
| private void interruptBlockedThreads() { |
| for (Thread thread : blockedThreads) |
| thread.interrupt(); |
| } |
| } |