My project has a button. When the button is pushed, it executes a function, SimulateDataUpload
. This SimulateDataUpload is locked with lock.lock()
. There is a for loop, packaged in a runnable, which updates a progress bar. The onRun of the runnable also locks with lock.lock()
. When I click the button two times quickly, it's clear the for loop has not finished iterating, so the lock shouldn't be released. Why does the code enter the SimulateDataUpload
again?
Code in MainActivity:
// defined before onCreate();
private Lock lock = new ReentrantLock();
// defined in onCreate();
simulateDataUploadButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
SimulateDataUpload();
}
});
public void SimulateDataUpload()
{
lock.lock();
try
{
Log.i("myTag", "Thread name: " + Thread.currentThread().getName()
+ ". Inside SimulateDataUpload. lock acquired.");
Handler uiOperationsHandler = new Handler();
setUiElements(true);
Runnable simulateDataUploadRunnable = new Runnable()
{
@Override
public void run()
{
lock.lock();
try
{
Log.i("myTag", "Thread name: "
+ Thread.currentThread().getName()
+ ". Inside runnable threadLockObject. lock
acquired.");
for (int i = 0; i < maxProgressBar + 1; i++)
{
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
int finalI = i;
uiOperationsHandler.post(new Runnable()
{
@Override
public void run()
{
Log.i("myTag", "Thread name: "
+ Thread.currentThread().getName()
+ ". i: " + finalI
+ ". Inside
uiOperationsHandler.post.");
taskCompletedEventListener.
getCallBackFunction().
onTaskCompletedEvent(finalI ==
maxProgressBar);
}
});
}
}
finally
{
lock.unlock();
Log.i("myTag", "Thread name: "
+Thread.currentThread().getName()
+". lock released.");
}
}
};
Thread simulateDataUploadThread = new
Thread(simulateDataUploadRunnable);
simulateDataUploadThread.start();
}
finally
{
lock.unlock();
Log.i("myTag", "Thread name: " +Thread.currentThread().getName()
+". lock released.");
}
}
Here is the normal output when button is only clicked once:
I/myTag: Thread name: main. Inside SimulateDataUpload. lock acquired.
I/myTag: Thread name: Thread-8. Inside runnable threadLockObject. lock acquired.
I/myTag: Thread name: main. lock released.
I/myTag: Thread name: main. i: 0. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 1. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 2. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 3. Inside uiOperationsHandler.post.
I/myTag: Thread name: Thread-8. lock released.
Here is the output when the button is clicked twice. I don't understand why I'm entering "Inside SimulateDataUpload..." a second time when it's clear the runnable with the locked object hasn't released the lock yet (hasn't finished its for loop iterations):
I/myTag: Thread name: main. Inside SimulateDataUpload. lock acquired.
I/myTag: Thread name: main. lock released.
I/myTag: Thread name: Thread-11. Inside runnable threadLockObject. lock acquired.
I/myTag: Thread name: main. i: 0. Inside uiOperationsHandler.post.
I/myTag: Thread name: Thread-11. lock released.
I/myTag: Thread name: main. Inside SimulateDataUpload. lock acquired.
I/myTag: Thread name: main. lock released.
I/myTag: Thread name: Thread-12. Inside runnable threadLockObject. lock acquired.
I/myTag: Thread name: main. i: 1. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 2. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 3. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 0. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 1. Inside uiOperationsHandler.post.
I/myTag: Thread name: main. i: 2. Inside uiOperationsHandler.post.
I/myTag: Thread name: Thread-12. lock released.
I/myTag: Thread name: main. i: 3. Inside uiOperationsHandler.post.
due to logs synchronised
keyword worked like a charm, you have created two Thread
s and started them, never interrupted, no reference kept. second one must wait for ending execution of first as method/code is synchronised with same final
object. so both Thread
s will start but second one will execute in fact after first ends
Took @Shark suggestion and used a reentrant lock
. Used the lock's isLocked()
features to strengthen the solution. Original code used synchronized (lockObject)
. The separate thread was prematurely releasing the lock, so clicking on button multiple times would give strange results. If anyone can explain why it behaved that way, I'm definitely curious to know.
Anyway, here is the working solution:
//synchronize (myObject) doesn't work in this example... so using a ReentrantLock.
private ReentrantLock lock = new ReentrantLock();
private final int maxProgressBar = 6;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// ... Other stuff defined here
simulateDataUploadButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
// Problem: The user can click multiple times and our runnable will be concurrently run in multiple different threads
// Used a lock to prevent this.
// 1. SimulateDataUpload. Lock Acquired by Main Thread. A runnable package is created and a new thread with that package is started. Lock released by Main Thread.
// 2. New thread runs and tries to acquire the lock, but is blocked until SimulateDataUpload releases the lock.
// 3. The new Thread acquires the lock.
// 4. The new Thread finishes the for loop and releases the lock.
SimulateDataUpload();
}
});
}
public void SimulateDataUpload()
{
if(lock.isLocked())
{
// If we are in the middle of an operation with the lock, return.
return;
}
else
{
lock.lock();
}
try
{
Log.i("myTag", "Thread name: " + Thread.currentThread().getName() + ". Inside SimulateDataUpload. lock acquired.");
Handler uiOperationsHandler = new Handler();
setUiElements(true);
Runnable simulateDataUploadRunnable = new Runnable()
{
@Override
public void run()
{
while (lock.isLocked())
{
// If we call this runnable with simulateDataUploadThread.start(), we need it to complete. So just wait for whatever has a lock to release the lock.
}
lock.lock();
try
{
Log.i("myTag", "Thread name: " + Thread.currentThread().getName() + ". Inside runnable threadLockObject. lock acquired.");
for (int i = 0; i < maxProgressBar + 1; i++)
{
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
Log.i("myTag", "Thread name: " + "exception: " +e);
}
// Fire Event. Use a handler because we are going to be updating UI elements.
int finalI = i;
uiOperationsHandler.post(new Runnable()
{
@Override
public void run() {
Log.i("myTag", "Thread name: " + Thread.currentThread().getName() + ". i: " + finalI + ". Inside uiOperationsHandler.post.");
taskCompletedEventListener.
getCallBackFunction().
onTaskCompletedEvent(finalI == maxProgressBar);
}
});
}
}
finally
{
lock.unlock();
Log.i("myTag", "Thread name: " +Thread.currentThread().getName() +". lock released.");
}
}
};
Thread simulateDataUploadThread = new Thread(simulateDataUploadRunnable);
simulateDataUploadThread.start();
}
finally
{
lock.unlock();
Log.i("myTag", "Thread name: " +Thread.currentThread().getName() +". lock released.");
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.