java thread safety between multiple variables - java

I have a Metrics class that's supposed to keep track of how many transactions we process each second and how long they take. The relevant part of its structure looks like this:
public class Metrics {
AtomicLong sent = new AtomicLong();
AtomicLong totalElapsedMsgTime = new AtomicLong();
AtomicLong sentLastSecond = new AtomicLong();
AtomicLong avgTimeLastSecond = new AtomicLong();
public void outTick(long elapsedMsgTime){
sent.getAndIncrement();
totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
class CalcMetrics extends TimerTask {
#Override
public void run() {
sentLastSecond.set(sent.getAndSet(0));
long tmpElapsed = totalElapsedMsgTime.getAndSet(0);
long tmpSent = sentLastSecond.longValue();
if(tmpSent != 0) {
avgTimeLastSecond.set(tmpElapsed / tmpSent);
} else {
avgTimeLastSecond.set(0);
}
}
}
}
My issue is that the outTick function will get called hundreds of times a second from lots of different threads. Being AtomicLong already ensures that each variable is individually thread safe, and they don't interact with each other in that function, so I don't want a lock that will make one call to outTick block another thread's call to outTick. It's perfectly fine if a couple of different threads increment the sent variable and then they both add to the totalElapsedMsgTime variable.
However, once it gets into CalcMetrics run method (which only happens once each second), they do interact. I want to ensure that I can pick up and reset both of those variables without being in the middle of an outTick call or having another outTick call occur between picking up one variable and the next.
Is there any way of doing this? (Does my explanation even make sense?) Is there a way of saying that A cannot interleave with B but multiple B's can interleave with each other?
EDIT:
I went with the ReadWriteLock that James suggested. Here's what my result looks like for anyone interested:
public class Metrics {
AtomicLong numSent = new AtomicLong();
AtomicLong totalElapsedMsgTime = new AtomicLong();
long sentLastSecond = 0;
long avgTimeLastSecond = 0;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
public void outTick(long elapsedMsgTime) {
readLock.lock();
try {
numSent.getAndIncrement();
totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
finally
{
readLock.unlock();
}
}
class CalcMetrics extends TimerTask {
#Override
public void run() {
long elapsed;
writeLock.lock();
try {
sentLastSecond = numSent.getAndSet(0);
elapsed = totalElapsedMsgTime.getAndSet(0);
}
finally {
writeLock.unlock();
}
if(sentLastSecond != 0) {
avgTimeLastSecond = (elapsed / sentLastSecond);
} else {
avgTimeLastSecond = 0;
}
}
}
}

The usual solution is to wrap all variables as one atomic data type.
class Data
{
long v1, v2;
Data add(Data another){ ... }
}
AtomicReference<Data> aData = ...;
public void outTick(long elapsedMsgTime)
{
Data delta = new Data(1, elapsedMsgTime);
aData.accumulateAndGet( delta, Data:add );
}
In your case, it may not be much faster than just locking.
There is another interesting lock in java8 - StampedLock . The javadoc example pretty much matches your use case. Basically, you can do optimistic reads on multiple variables; afterwards, check to make sure that no writes were done during the reads. In your case, "hundreds" of writes per second, the optimistic reads mostly would succeed.

Sounds like you need a reader/writer lock. (java.util.concurrent.locks.ReentrantReadWriteLock).
Your outTick() function would lock the ReaderLock. Any number of threads are allowed to lock the ReaderLock at the same time.
Your calcMetrics() would lock the WriterLock. No new readers are allowed in once a thread is waiting for the writer lock, and the writer is not allowed in until all the readers are out.
You would still need the atomics to protect the individual counters that are incremented by outTick().

Use locks ( https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html ). Once you implement locks you'll have finer control. An additional side effect will be that you won't need to use AtomicLong anymore (although you still can); you can use volatile long instead, which would be more efficient. I did not make that change in the example.
Basically just create a new Object:
private Object lock = new Object();
Then, use the synchronized keyword with that object around all the code that should never happen at the same time as another synchronized block with the same lock. Example:
synchronized(lock)
{
sent.getAndIncrement();
totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
So your whole program will look like this (note: untested code)
public class Metrics {
private Object lock = new Object();
AtomicLong sent = new AtomicLong();
AtomicLong totalElapsedMsgTime = new AtomicLong();
AtomicLong sentLastSecond = new AtomicLong();
AtomicLong avgTimeLastSecond = new AtomicLong();
public void outTick(long elapsedMsgTime){
synchronized (lock)
{
sent.getAndIncrement();
totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
}
class CalcMetrics extends TimerTask {
#Override
public void run() {
synchronized (lock)
{
sentLastSecond.set(sent.getAndSet(0));
long tmpElapsed = totalElapsedMsgTime.getAndSet(0);
long tmpSent = sentLastSecond.longValue();
if(tmpSent != 0) {
avgTimeLastSecond.set(tmpElapsed / tmpSent);
} else {
avgTimeLastSecond.set(0);
}
}
}
}
}
Edit: I threw together a quick (and ugly) efficiency test program and found that when I synchronize with locks, I get overall better performance. Note that the results of the first 2 runs are discarded because the timing results when the Java JIT still hasn't compiled all code paths to machine code are not representative of the long term runtime.
Results:
With Locks: 8365ms
AtomicLong: 21254ms
Code:
import java.util.concurrent.atomic.AtomicLong;
public class Main
{
private AtomicLong testA_1 = new AtomicLong();
private AtomicLong testB_1 = new AtomicLong();
private volatile long testA_2 = 0;
private volatile long testB_2 = 0;
private Object lock = new Object();
private volatile boolean a = false;
private volatile boolean b = false;
private volatile boolean c = false;
private static boolean useLocks = false;
public static void main(String args[])
{
System.out.println("Locks:");
useLocks = true;
test();
System.out.println("No Locks:");
useLocks = false;
test();
System.out.println("Locks:");
useLocks = true;
test();
System.out.println("No Locks:");
useLocks = false;
test();
}
private static void test()
{
final Main main = new Main();
new Thread()
{
public void run()
{
for (int i = 0; i < 80000000; ++i)
main.outTick(10);
main.a = true;
}
}.start();
new Thread()
{
public void run()
{
for (int i = 0; i < 80000000; ++i)
main.outTick(10);
main.b = true;
}
}.start();
new Thread()
{
public void run()
{
for (int i = 0; i < 80000000; ++i)
main.outTick(10);
main.c = true;
}
}.start();
long startTime = System.currentTimeMillis();
// Okay this isn't the best way to do this, but it's good enough
while (!main.a || !main.b || !main.c)
{
try
{
Thread.sleep(1);
} catch (InterruptedException e)
{
}
}
System.out.println("Elapsed time: " + (System.currentTimeMillis() - startTime) + "ms");
System.out.println("Test A: " + main.testA_1 + " " + main.testA_2);
System.out.println("Test B: " + main.testB_1 + " " + main.testB_2);
System.out.println();
}
public void outTick(long elapsedMsgTime)
{
if (!useLocks)
{
testA_1.getAndIncrement();
testB_1.getAndAdd(elapsedMsgTime);
}
else
{
synchronized (lock)
{
++testA_2;
testB_2 += elapsedMsgTime;
}
}
}
}

Related

Why can my two threads coordinate through non-volatile fields?

According to this specification, two java threads can not coordinate through non-volatile fields. Why is my code running okay?
public class TestVolatileExample {
static int pairCount = 1000;
static VolatileExample[] exps = new VolatileExample[pairCount];
static{
for(int i = 0;i<pairCount;i++){
exps[i] = new VolatileExample();
}
}
#Test
public void test() throws InterruptedException{
final int valuePair[][] = new int[pairCount][2];
Thread[] threads = new Thread[pairCount*2];
for(int i = 0;i<pairCount;i++){
final int index = i;
//final VolatileExample exp = new VolatileExample();
//writer
Thread writer = new Thread(new Runnable(){
#Override
public void run() {
VolatileExample exp = exps[index];
int val = new Random().nextInt(100);
valuePair[index][0] = val;
exp.set(val);
}
});
writer.start();
threads[i*2] = writer;
//reader
Thread reader = new Thread(new Runnable(){
#Override
public void run() {
VolatileExample exp = exps[index];
while(!exp.changed()){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println("waitting for change...");
}
int val = exp.get();
valuePair[index][1] = val;
}
});
reader.start();
threads[i*2+1] = reader;
}
for(Thread t : threads){
t.join();
}
for(int i = 0;i<pairCount;i++){
int write = valuePair[i][0];
int read = valuePair[i][1];
System.out.println(write+"," + read);
Assert.assertEquals(write,read);
}
}
}
public class VolatileExample {
private int x;
private boolean changed = false;
public void set(int x){
this.x = x;
this.changed = true;
System.out.println("changed...");
}
public int get(){
return x;
}
public boolean changed(){
return changed;
}
}
You see, the reader thread is waiting for the value x in VolatileExample until the flag property been changed. According to Java specification, the non-volatile property,'changed', will be saved in the respective cache of each thread. But why did my program get the expected results?
I started 1000 pairs of reading and write threads, and each read thread did read the values written by the write threads.
Is there anything wrong with me?
The page you linked to says:
The compiler is free to read the field this.done just once, and reuse
the cached value in each execution of the loop. This would mean that
the loop would never terminate, even if another thread changed the
value of this.done.
This means whether your code works or not depends on whether the compiler decides to cache your variables (doesn't work) or not (works). It is free to do so if it wants, but it doesn't have to.
So your code may or may not work depending on things that are not under your control.

Using threads to modify an object

I'm new to threads. I wanted to get two threads to increment an integer to a certain value. because int type is immutable, I switched to atomic integer. I also tried to wrap an int to a class and that didn't work either. I also tried static/volatile int and that didn't work. I also tried to use fairness policy. The main issue is that "counterObj" is not incremented correctly and is still set to 0 even though it is injected to both threads.
My expected running behavior:
thread value
thread 0 0
thread 1 1
thread 0 2
...
What I wrote so far:
import java.util.concurrent.atomic.AtomicInteger;
public class Application {
public static void main(String[] args) {
Application app = new Application();
try {
app.launch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void launch() throws InterruptedException {
int increments = 100;
AtomicInteger counterObj = new AtomicInteger(0);
CounterThread th1 = new CounterThread("1", counterObj, increments);
CounterThread th2 = new CounterThread("2", counterObj, increments);
th1.start();
th2.start();
System.out.println(counterObj.get());
}
}
and
import java.util.concurrent.atomic.AtomicInteger;
public class CounterThread implements Runnable {
private final String threadID;
private AtomicInteger counterObj;
private int bound;
public CounterThread(String threadID, AtomicInteger counter, int bound) {
this.threadID = threadID;
this.counterObj = counter;
this.bound = bound;
}
#Override
public synchronized void run() {
while (counterObj.get() < bound) {
synchronized (this) {
counterObj.incrementAndGet();
}
}
System.out.println("Thread " + threadID + " finished");
}
public void start() throws InterruptedException {
Thread thread = new Thread(this, threadID);
thread.join();
thread.start();
}
}
Cheers!
I think your program is exiting before your threads get a chance to do anything (probably due to the ordering of your starts and joins. I would move your thread starting logic into your main(or launch) method. Something like the following.
Thread thread1 = new Thread(new MyCounterRunnable("1", counterObj, increments));
Thread thread2 = new Thread(new MyCounterRunnable("2", counterObj, increments));
Then, in your main, you need to call join after starting the threads...as follows:
thread1.start(); // starts first thread.
thread2.start(); // starts second thread.
thread1.join(); // don't let main exit until thread 1 is done.
thread2.join(); // don't let main exit until thread 2 is done.
What you really are wanting is for only one thread to increment an int at a time.
The int variable is the resource you want in the synchronized block, so the different threads can increment it one at a time.
This can be done using syncrhonize alone.
Disclaimer: I didn't run the code so it could have some typo or Exceptions to be removed from the Application class.
public class Application {
private int theVar = 0;
private int increments = 100;
public static void main(String[] args) {
Application app = new Application();
try {
app.launch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized addOne(){
this.theVar++;
}
private void launch() throws InterruptedException {
Runnable counter1 = new Counter(this, increments), counter2 = new Counter(this, increments);
Thread t1 = new Thread(counter1);
Thread t2 = new Thread(counter2);
t1.start();
t2.start();
}
}
A counter class
public class Counter implements Runnable{
private Application app;
int rounds = -1;
public Counter(Application app, rounds){
this.app = app;
this.rounds = rounds;
}
public void run(){
while(int i=0; i<rounds; i++){
this.app.addOne();
}
}
}
AtomicInteger takes care of atomicity itself, so you shouldn't need to use synchronized -- but only if you play by the rules, and do your atomic operations in one call.
You're failing to do this, because you call counterObj.get() then depending on the result counterObj.incrementAndGet(). You need to avoid this because you want the check and the update to be part of the same atomic chunk of work.
You can get close with:
while(counterObj.incrementAndGet() < bound) {} ;
But this will always increment at least once, which may be once too many.
Slightly more involved:
IntUnaryOperator incrementWithLimit = x ->
( x < bound ? x + 1 : x );
while(counterObj.updateAndGet(incrementWithLimit) < bound) {};
That is, we've created a function that increments a number only if it's lower than bound, and we tell AtomicInteger to apply that.
There are a couple of issues with your code:
Thread.join method works only if the thread has started, else it does nothing. So you must reorder your code, but if you just move the join method after start, when starting the first thread by calling CounterThread.start, the main thread will wait until the started thread has finished, blocked in the Thread.join method, and only then will continue to starting the second one. A solution is to make an additional method in the CounterThread class, that will be called after both threads have been started:
public void waitFinish() throws InterruptedException {
thread.join();
}
synchronized (this) is synchronizing on the CounterThread instance that has been created when you called new CounterThread(...), but you have two instances so each will be synchronizing on a different object. For synchronized to work, you need to use a common instance of an object, in this case you can use the shared counterObj.
Only the AtomicInteger methods are guaranteed to be thread safe, so after you check if the bound has been reached outside the synchronized block, when entering the synchronized block the value can already be changed by another thread. So you need to do a recheck inside the synchronized block OR to first synchronize on the shared lock(counterObj) before the check and increment.
while (true) {
synchronized (counterObj) {
if (counterObj.get() < bound)
counterObj.incrementAndGet();
else break;
}
}
Note that the AtomicInteger class synchronized methods aren't helping now, but because it is a mutable object, it helps to use it as a shared lock. If you used an Integer instead, being immutable, a new instance will have been created when you incremented it. So now, it's only function is a wrapper holding the integer result.
Putting it all together:
public class Application {
public static void main(String[] args) {
Application app = new Application();
try {
app.launch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void launch() throws InterruptedException {
int increments = 100;
AtomicInteger counterObj = new AtomicInteger(0);
CounterThread th1 = new CounterThread("1", counterObj, increments);
CounterThread th2 = new CounterThread("2", counterObj, increments);
th1.start();
th2.start();
th1.waitFinish();
th2.waitFinish();
System.out.println(counterObj.get());
}
}
public class CounterThread implements Runnable {
private final String threadID;
private AtomicInteger counterObj;
private int bound;
private Thread thread;
public CounterThread(String threadID, AtomicInteger counter, int bound) {
this.threadID = threadID;
this.counterObj = counter;
this.bound = bound;
}
#Override
public void run() {
while (true) {
synchronized (counterObj) {
if (counterObj.get() < bound)
counterObj.incrementAndGet();
else break;
}
}
System.out.println("Thread " + threadID + " finished");
}
public void start() throws InterruptedException {
thread = new Thread(this, threadID);
thread.start();
}
public void waitFinish() throws InterruptedException {
thread.join();
}
}
I've included a double check on the AtomicInteger, this appears to be what you've been trying to accomplish.
import java.util.concurrent.atomic.AtomicInteger;
public class DualCounters{
public static void main(String[] args) throws Exception{
AtomicInteger i = new AtomicInteger(0);
int bounds = 3;
Thread a = new Thread(()->{
int last = 0;
while(i.get()<bounds){
synchronized(i){
if(i.get()<bounds){
last = i.getAndIncrement();
}
}
}
System.out.println("a last " + last);
});
Thread b = new Thread(()->{
int last = 0;
while(i.get()<bounds){
synchronized(i){
if(i.get()<bounds){
last = i.getAndIncrement();
}
}
}
System.out.println("b last " + last);
});
a.start();
b.start();
a.join();
b.join();
System.out.println(i.get() + " afterwards");
}
}
The double check is a broken concept in java, the AtomicInteger offers tools for accomplishing this without any synchronization.
int a;
while((a = i.getAndIncrement())<bounds){
...
}
Now a will never be greater than bounds inside of the while loop. When the loop is finished i and a could have a value greater than bounds.
If that was an issue, there is always the other method getAndUpdate
while((a = i.getAndUpdate(i->i<bounds?i+1:i)<bounds){
...
}

How to invoke two threads at same time?

I am trying to write Thread Interference Example.
Below is my code:
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
Suppose Thread A invokes increment at about the same time Thread B invokes decrement.
How to implement this one.
There is not guarantee how they will run it depends on OS scheduler. There is nothing better than this
Thread a = new ThreadA();
Thread b = new ThreadB();
a.start();
b.start();
To get two threads to start executing at the same time you can use a latch. (Which is to say, two threads that become available for execution as close together as possible.) Still for a single increment/decrement each it will probably take many runs to observe an interference. For a repeatable experiment you probably want to call increment/decrement several times in parallel and observe the final value of c.
final Counter counter = new Counter()
final CountDownLatch latch = new CountDownLatch(1);
Thread thread1 = new Thread(new Runnable() {
public void run() {
latch.await();
for (int i = 0; i < 100; i++) {
counter.increment();
}
}}).start():
Thread thread2 = new Thread(new Runnable() {
public void run() {
latch.await();
for (int i = 0; i < 100; i++) {
counter.decrement();
}
}}).start():
Thread.sleep(10);//give thread 2 a timeslice to hit the await
latch.countDown();
System.out.println(counter.value()); //non-zero value indicates interference
Now in this example if you try to execute and the output false shows interference.
How it works:
Both the Runnables keep a thread local count which is incremented for each invocation of increment() and decrement(). So after execution for some amount of time if we try to validate the values
Then you can say that:
value of Counter = invocation of increment() - invocation of decrement().
But when you try to verify this at the end of execution you get false. Which shows that the actual counter value was not as expected.
public static void main(String[] args) throws InterruptedException
{
Counter c = new Counter();
IncrementingRunnable incRunnable = new IncrementingRunnable(c);
DecrementingRunnable decRunnable = new DecrementingRunnable(c);
Thread tA = new Thread(incRunnable);
Thread tB = new Thread(decRunnable);
tA.start();tB.start();
Thread.sleep(10000);
stop = true;
tA.join();
tB.join();
//verify value
int actualCount = c.c;
int expectedCount = incRunnable.count - decRunnable.count;
System.out.println(actualCount == expectedCount);
}
public static volatile boolean stop = false;
static class IncrementingRunnable implements Runnable{
volatile int count = 0;
private Counter counter;
public IncrementingRunnable(Counter c) {
this.counter = c;
}
#Override
public void run() {
while(!stop){
counter.increment();
count++;
}
}
}
static class DecrementingRunnable implements Runnable{
volatile int count = 0;
private Counter counter;
public DecrementingRunnable(Counter c) {
this.counter = c;
}
#Override
public void run() {
while(!stop){
counter.decrement();
count++;
}
}
}
Now try changing the primitive c in Counter to AtomicInteger and see the output again. You will find that now the output is true.

Uses of volatile without synchronization

Knowing that
Reads and writes are atomic for all variables declared volatile
Question1: Can this be understood as if
private volatile int x = 0;
x++; operation is atomic?
And that
Marking variable volatile does not eliminate all need to synchronize
atomic actions, because memory consistency errors are still possible.
Question2: I wonder under what circumstances (if any) it is possible to see a variable marked volatile and not see any methods of blocks marked synchronized (that attempt to access/ modify the variable)?
In other words, should all variables that need to be protected from concurrent modification be marked volatile?
The volatile only gives you additional visibility guarantees, atomic writes/reads for longs/doubles (otherwise not guaranteed by the JLS, yes) and some memory order guarantees. No synchronization (it is possible though to build synchronization blocks starting with just volatile - Dekker's algorithm )
So no, it does not help you with x++ - that's still a read, inc and write and needs some form of synchronization.
One example of volatile is the famous double-checked locking, where we avoid synchronization most of the time because the ordering guarantees are all we need:
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
An example where there's absolutely no synchronization involved, is a simple exit flag, here it's not about ordering guarantees but only about the guaranteed visibility
public volatile boolean exit = false;
public void run() {
while (!exit) doStuff();
// exit when exit set to true
}
If another thread sets exit = true the other thread doing the while loop is guaranteed to see the update - without volatile it may not.
x++; operation is atomic?
No. This reduces to x = x + 1. The read of x is atomic, and the write to x is atomic, but x = x + 1 as a whole is not atomic.
I wonder under what circumstances (if any) it is possible to see a variable marked volatile and not see any methods of blocks marked synchronized (that attempt to access/ modify the variable)?
Well, there are all kinds of approaches to concurrency that don't use synchronized. There's a wide variety of other locking utilities in Java, and lock-free algorithms that still require things like volatile: ConcurrentLinkedQueue is a specific example, though it makes extensive use of "magical" compareAndSet atomics.
As a quickly testable example that may illustrate the previous answers, this yields always a final count of 8:
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest_synchronize {
public static void main(String[] args) {
ThreadTest_synchronize tt = new ThreadTest_synchronize ();
try {
tt.go();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void go() throws InterruptedException{
MyRunnable t = new MyRunnable();
Thread myThread_1 = new Thread( t, "t1");
Thread myThread_2 = new Thread( t, "t2");
myThread_1.start();
myThread_2.start();
myThread_1.join();
myThread_2.join();
System.out.println("Processing count="+t.getCount());
}
private class MyRunnable implements Runnable{
private AtomicInteger count=new AtomicInteger(0);
#Override
public void run() {
for(int i=1; i< 5; i++){
doSomething(i);
count.getAndAdd(1);
}
}
public AtomicInteger getCount() {
return this.count;
}
private void doSomething(int i) {
try {
Thread.sleep(i*300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
while this generally does not:
public class ThreadTest_volatile {
public static void main(String[] args) {
ThreadTest_volatile tt = new ThreadTest_volatile ();
try {
tt.go();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void go() throws InterruptedException{
MyRunnable t = new MyRunnable();
Thread myThread_1 = new Thread( t, "t1");
Thread myThread_2 = new Thread( t, "t2");
myThread_1.start();
myThread_2.start();
myThread_1.join();
myThread_2.join();
System.out.println("Processing count="+t.getCount());
}
private class MyRunnable implements Runnable{
private volatile int count = 0;
#Override
public void run() {
for(int i=1; i< 5; i++){
doSomething(i);
count++;
}
}
private int add(int count){
return ++count;
}
public int getCount(){
return count;
}
private void doSomething(int i) {
try {
Thread.sleep(i*300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

How can I write a semaphore in Java which prioritizes previous successful applicants?

I have a need for a single-permit semaphore object in my Java program where there is an additional acquire method which looks like this:
boolean tryAcquire(int id)
and behaves as follows: if the id has not been encountered before, then remember it and then just do whatever java.util.concurrent.Semaphore does. If the id has been encountered before and that encounter resulted in the lease of the permit then give this thread priority over all other threads who may be waiting for the permit. I'll also want an extra release method like:
void release(int id)
which does whatever the java.util.concurrent.Semaphore does, plus also "forgets" about the id.
I don't really know how to approach this, but here's the start of a possible implementation but I fear it's going nowhere:
public final class SemaphoreWithMemory {
private final Semaphore semaphore = new Semaphore(1, true);
private final Set<Integer> favoured = new ConcurrentSkipListSet<Integer>();
public boolean tryAcquire() {
return semaphore.tryAcquire();
}
public synchronized boolean tryAcquire(int id) {
if (!favoured.contains(id)) {
boolean gotIt = tryAcquire();
if (gotIt) {
favoured.add(id);
return true;
}
else {
return false;
}
}
else {
// what do I do here???
}
}
public void release() {
semaphore.release();
}
public synchronized void release(int id) {
favoured.remove(id);
semaphore.release();
}
}
EDIT:
Did some experiment. Please see this answer for results.
In principle, Semaphore has a queue of threads internally, so like Andrew says if you make this queue a priority queue and poll from this queue to give out permits, it probably behaves the way you want. Note that you can't do this with tryAcquire because that way threads don't queue up. From what I see looks like you'd have to hack the AbstractQueuedSynchronizer class to do this.
I could also think of a probabilistic approach, like this:
(I'm not saying that the code below would be a good idea! Just brainstorming here. )
public class SemaphoreWithMemory {
private final Semaphore semaphore = new Semaphore(1);
private final Set<Integer> favoured = new ConcurrentSkipListSet<Integer>();
private final ThreadLocal<Random> rng = //some good rng
public boolean tryAcquire() {
for(int i=0; i<8; i++){
Thread.yield();
// Tend to waste more time than tryAcquire(int id)
// would waste.
if(rng.get().nextDouble() < 0.3){
return semaphore.tryAcquire();
}
}
return semaphore.tryAcquire();
}
public boolean tryAcquire(int id) {
if (!favoured.contains(id)) {
boolean gotIt = semaphore.tryAcquire();
if (gotIt) {
favoured.add(id);
return true;
} else {
return false;
}
} else {
return tryAquire();
}
}
Or have the "favoured" threads hang out a little bit longer like this:
EDIT: Turns out this was a very bad idea (with both fair and non-fair semaphore) (see my experiment for details.
public boolean tryAcquire(int id) {
if (!favoured.contains(id)) {
boolean gotIt = semaphore.tryAcquire(5,TimeUnit.MILLISECONDS);
if (gotIt) {
favoured.add(id);
return true;
} else {
return false;
}
} else {
return tryAquire();
}
I guess this way you can bias the way permits are issued, while it won't be fair. Though with this code you'd probably be wasting a lot of time performance wise...
For blocking acquisition model, what about this:
public class SemWithPreferred {
int max;
int avail;
int preferredThreads;
public SemWithPreferred(int max, int avail) {
this.max = max;
this.avail = avail;
}
synchronized public void get(int id) throws InterruptedException {
boolean thisThreadIsPreferred = idHasBeenServedSuccessfullyBefore(id);
if (thisThreadIsPreferred) {
preferredThreads++;
}
while (! (avail > 0 && (preferredThreads == 0 || thisThreadIsPreferred))) {
wait();
}
System.out.println(String.format("granted, id = %d, preferredThreads = %d", id, preferredThreads));
avail -= 1;
if (thisThreadIsPreferred) {
preferredThreads--;
notifyAll(); // removal of preferred thread could affect other threads' wait predicate
}
}
synchronized public void put() {
if (avail < max) {
avail += 1;
notifyAll();
}
}
boolean idHasBeenServedSuccessfullyBefore(int id) {
// stubbed out, this just treats any id that is a
// multiple of 5 as having been served successfully before
return id % 5 == 0;
}
}
Assuming that you want the threads to wait, I hacked a solution that is not perfect, but should do.
The idea is to have two semaphores and a "favourite is waiting" flag.
Every thread that tries to acquire the SemaphoreWithMemory first tries to acquire the "favouredSemaphore". A "favoured" thread keeps the Semaphore and a non-favoured releases it immediately. Thereby the favoured thread blocks all other incoming threads once he has acquired this Semaphore.
Then the second "normalSemaphore" has to be acquired to finish up.
But the non-favoured thread then checks again that there is no favoured thread waiting using a volatile variable). If none is waiting then he simply continues; if one is waiting, he releases the normalSemaphore and recursively calls acquire again.
I am not really sure that there are no race conditions lurking. If you want to be sure, you perhaps should refactor your code to hand of "work items" to a priority queue, where another thread takes the work item with the highest priority and executes that code.
public final class SemaphoreWithMemory {
private volatile boolean favouredAquired = false;
private final Semaphore favouredSemaphore = new Semaphore(1, true);
private final Semaphore normalSemaphore = new Semaphore(1, true);
private final Set<Integer> favoured = new ConcurrentSkipListSet<Integer>();
public void acquire() throws InterruptedException {
normalSemaphore.acquire();
}
public void acquire(int id) throws InterruptedException {
boolean idIsFavoured = favoured.contains(id);
favouredSemaphore.acquire();
if (!idIsFavoured) {
favouredSemaphore.release();
} else {
favouredAquired = true;
}
normalSemaphore.acquire();
// check again that there is no favoured thread waiting
if (!idIsFavoured) {
if (favouredAquired) {
normalSemaphore.release();
acquire(); // starving probability!
} else {
favoured.add(id);
}
}
}
public void release() {
normalSemaphore.release();
if (favouredAquired) {
favouredAquired = false;
favouredSemaphore.release();
}
}
public void release(int id) {
favoured.remove(id);
release();
}
}
I read this article by Ceki and was interested how biased semaphore acquisition could be (since I felt the "biased locking" behavior would make sense in semaphores as well..). On my hardware with 2 processors and a Sun JVM 1.6, it actually results in pretty uniform lease.
Anyways, I also tried to "bias" the leasing of semaphore with the strategy I wrote in my other answer. Turns out a simple extra yield statement alone results in significant bias. Your problem is more complicated, but perhaps you can do similar tests with your idea and see what you get :)
NOTE The code below is based upon Ceki's code here
Code:
import java.util.concurrent.*;
public class BiasedSemaphore implements Runnable {
static ThreadLocal<Boolean> favored = new ThreadLocal<Boolean>(){
private boolean gaveOut = false;
public synchronized Boolean initialValue(){
if(!gaveOut){
System.out.println("Favored " + Thread.currentThread().getName());
gaveOut = true;
return true;
}
return false;
}
};
static int THREAD_COUNT = Runtime.getRuntime().availableProcessors();
static Semaphore SEM = new Semaphore(1);
static Runnable[] RUNNABLE_ARRAY = new Runnable[THREAD_COUNT];
static Thread[] THREAD_ARRAY = new Thread[THREAD_COUNT];
private int counter = 0;
public static void main(String args[]) throws InterruptedException {
printEnvironmentInfo();
execute();
printResults();
}
public static void printEnvironmentInfo() {
System.out.println("java.runtime.version = "
+ System.getProperty("java.runtime.version"));
System.out.println("java.vendor = "
+ System.getProperty("java.vendor"));
System.out.println("java.version = "
+ System.getProperty("java.version"));
System.out.println("os.name = "
+ System.getProperty("os.name"));
System.out.println("os.version = "
+ System.getProperty("os.version"));
}
public static void execute() throws InterruptedException {
for (int i = 0; i < THREAD_COUNT; i++) {
RUNNABLE_ARRAY[i] = new BiasedSemaphore();
THREAD_ARRAY[i] = new Thread(RUNNABLE_ARRAY[i]);
System.out.println("Runnable at "+i + " operated with "+THREAD_ARRAY[i]);
}
for (Thread t : THREAD_ARRAY) {
t.start();
}
// let the threads run for a while
Thread.sleep(10000);
for (int i = 0; i< THREAD_COUNT; i++) {
THREAD_ARRAY[i].interrupt();
}
for (Thread t : THREAD_ARRAY) {
t.join();
}
}
public static void printResults() {
System.out.println("Ran with " + THREAD_COUNT + " threads");
for (int i = 0; i < RUNNABLE_ARRAY.length; i++) {
System.out.println("runnable[" + i + "]: " + RUNNABLE_ARRAY[i]);
}
}
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (favored.get()) {
stuff();
} else {
Thread.yield();
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
stuff();
}
}
}
private void stuff() {
if (SEM.tryAcquire()) {
//favored.set(true);
counter++;
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
SEM.release();
} else {
//favored.set(false);
}
}
public String toString() {
return "counter=" + counter;
}
}
Results:
java.runtime.version = 1.6.0_21-b07
java.vendor = Sun Microsystems Inc.
java.version = 1.6.0_21
os.name = Windows Vista
os.version = 6.0
Runnable at 0 operated with Thread[Thread-0,5,main]
Runnable at 1 operated with Thread[Thread-1,5,main]
Favored Thread-0
Ran with 2 threads
runnable[0]: counter=503
runnable[1]: counter=425
Tried with 30 seconds instead of 10:
java.runtime.version = 1.6.0_21-b07
java.vendor = Sun Microsystems Inc.
java.version = 1.6.0_21
os.name = Windows Vista
os.version = 6.0
Runnable at 0 operated with Thread[Thread-0,5,main]
Runnable at 1 operated with Thread[Thread-1,5,main]
Favored Thread-1
Ran with 2 threads
runnable[0]: counter=1274
runnable[1]: counter=1496
P.S.: Looks like "hanging out" was a very bad idea. When I tried calling SEM.tryAcquire(1,TimeUnit.MILLISECONDS); for favored threads and SEM.tryAcquire() for non-favored threads, non-favored threads got the permit almost 5 times more than the favored thread!
Also, I'd like to add that these results are only measured under 1 particular situation, so it's not clear how these measures behave in other situations.
It strikes me that the simplest way to do this is not to try and combine Semaphores, but to build it from scratch on top of monitors. This is generally risky, but in this case, as there are no good building blocks in java.util.concurrent, it's the clearest way to do it.
Here's what i came up with:
public class SemaphoreWithMemory {
private final Set<Integer> favouredIDs = new HashSet<Integer>();
private final Object favouredLock = new Object();
private final Object ordinaryLock = new Object();
private boolean available = true;
private int favouredWaiting = 0;
/**
Acquires the permit. Blocks until the permit is acquired.
*/
public void acquire(int id) throws InterruptedException {
Object lock;
boolean favoured = false;
synchronized (this) {
// fast exit for uncontended lock
if (available) {
doAcquire(favoured, id);
return;
}
favoured = favouredIDs.contains(id);
if (favoured) {
lock = favouredLock;
++favouredWaiting;
}
else {
lock = ordinaryLock;
}
}
while (true) {
synchronized (this) {
if (available) {
doAcquire(favoured, id);
return;
}
}
synchronized (lock) {
lock.wait();
}
}
}
private void doAcquire(boolean favoured, int id) {
available = false;
if (favoured) --favouredWaiting;
else favouredIDs.add(id);
}
/**
Releases the permit.
*/
public synchronized void release() {
available = true;
Object lock = (favouredWaiting > 0) ? favouredLock : ordinaryLock;
synchronized (lock) {
lock.notify();
}
}
}

Categories