Skip to content
This repository has been archived by the owner on Apr 9, 2020. It is now read-only.

Commit

Permalink
Lock returned
Browse files Browse the repository at this point in the history
  • Loading branch information
LSafer committed Apr 4, 2020
1 parent 61e9a00 commit 66873f8
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 4 deletions.
8 changes: 4 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ repositories {
}

dependencies {
compileOnly 'com.github.cufyorg:util:0.1.0-beta2'
compileOnly 'com.github.cufyorg:base:0.1.0-beta4'
compileOnly 'com.github.cufyorg:util:0.1.0'
compileOnly 'com.github.cufyorg:base:0.1.0'

testImplementation 'com.github.cufyorg:util:0.1.0-beta2'
testImplementation 'com.github.cufyorg:base:0.1.0-beta4'
testImplementation 'com.github.cufyorg:util:0.1.0'
testImplementation 'com.github.cufyorg:base:0.1.0'
testImplementation 'junit:junit:4.11'
}

Expand Down
229 changes: 229 additions & 0 deletions src/main/java/cufy/concurrent/Lock.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Copyright (c) 2019, LSafer, All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* -You can edit this file (except the header).
* -If you have change anything in this file. You
* shall mention that this file has been edited.
* By adding a new header (at the bottom of this header)
* with the word "Editor" on top of it.
*/
package cufy.concurrent;

import cufy.lang.IllegalThreadException;

import java.io.Closeable;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
* A java lock locker thread. Designed to be a replacement of wrapping the code with 'synchronized' statement. By creating a thread on the background
* to {@link #lock()} that lock and hold it until {@link #unlock()} get invoked.
*
* @param <T> the type of that lock
* @author LSaferSE
* @version 2 release (29-Dec-2019)
* @since 07-Dec-2019
*/
public class Lock<T> extends Thread implements Closeable {
/**
* The lock holder should end it's thread.
*/
final static protected int CLOSED = -1;
/**
* The lock holder should gain it's targeted lock.
*/
final static protected int LOCKED = 1;
/**
* The lock holder should release it's targeted lock.
*/
final static protected int UNLOCKED = 0;

/**
* The targeted lock.
*/
final protected T lock;
/**
* The thread that have created this. To avoid serving other threads.
*/
final protected Thread master;
/**
* The reference to communicate with the lock holder thread. Representing the state integer code.
*/
final protected AtomicInteger state = new AtomicInteger(CLOSED);

/**
* Initialize a new lock holder.
*
* @param lock the targeted lock
* @throws NullPointerException if the given lock is null
*/
public Lock(T lock) {
Objects.requireNonNull(lock, "lock");
this.setDaemon(true);
this.lock = lock;
this.master = Thread.currentThread();
}

/**
* Initialise a new lock. Locking on itself.
*/
public Lock() {
this.setDaemon(true);
this.lock = (T) this;
this.master = Thread.currentThread();
}

/**
* {@inheritDoc}
*
* @throws IllegalThreadException if the caller thread isn't the owner thread of this lock
*/
@Override
public void close() {
this.assertMasterThread();
synchronized (this.state) {
this.state.set(CLOSED);
this.state.notify();
}
try {
this.join();
} catch (InterruptedException ignored) {
}
}

/**
* {@inheritDoc}
*
* @throws IllegalThreadException if the caller thread is not this
*/
@Override
public void run() {
this.assertThisThread();
while (true) {
synchronized (this.state) {
switch (this.state.get()) {
case UNLOCKED:
this.release0();
break;
case LOCKED:
this.lock0();
break;
case CLOSED:
this.close0();
return;
default:
throw new IllegalStateException(this.state.toString());
}
}
}
}

/**
* Hold the lock. Wait for the lock been gained.
*
* @throws IllegalThreadStateException if this lock already closed
* @throws IllegalThreadException if the caller thread isn't the master of this
*/
public void lock() {
this.assertMasterThread();
synchronized (this.state) {
if (!this.isAlive())
this.start();
try {
this.state.set(LOCKED);
this.state.notify();
this.state.wait();
} catch (InterruptedException ignored) {
}
}
}

/**
* Release the targeted lock by this.
*
* @throws IllegalThreadException if the caller thread isn't the master of this
*/
public void unlock() {
this.assertMasterThread();
synchronized (this.state) {
if (!this.isAlive())
return;
try {
this.state.set(UNLOCKED);
this.state.notify();
this.state.wait();
} catch (InterruptedException ignored) {
}
}
}

/**
* Do code before closing the lock.
*
* @throws IllegalThreadException if the caller thread isn't this thread
*/
protected void close0() {
this.assertThisThread();
synchronized (this.state) {
this.state.notify();
}
}

/**
* Sleep with while owning the lock.
*
* @throws IllegalThreadException if the caller thread isn't this thread
* @implSpec notify {@link #state} before sleeping
*/
protected void lock0() {
this.assertThisThread();
synchronized (this.state) {
synchronized (this.lock) {
try {
this.state.notify();
this.state.wait();
} catch (InterruptedException ignored) {
}
}
}
}

/**
* Sleep until notified.
*
* @throws IllegalThreadException if the caller thread isn't this thread
*/
protected void release0() {
this.assertThisThread();
synchronized (this.state) {
try {
this.state.notify();
this.state.wait();
} catch (InterruptedException ignored) {
}
}
}

/**
* Assert that the caller thread is the master of this lock.
*
* @throws IllegalThreadException if the caller thread isn't the master of this
*/
private void assertMasterThread() {
Thread current = Thread.currentThread();
if (current != this.master)
throw new IllegalThreadException(current + " isn't the master thread of " + this);
}

/**
* Assert that the caller thread is this lock.
*
* @throws IllegalThreadException if the caller thread isn't this thread
*/
private void assertThisThread() {
Thread current = Thread.currentThread();
if (current != this)
throw new IllegalThreadException(current + " tries to invoke a method that is exclusive to " + this);
}
}
89 changes: 89 additions & 0 deletions src/test/java/cufy/concurrent/LockTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2019, LSafer, All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* -You can edit this file (except the header).
* -If you have change anything in this file. You
* shall mention that this file has been edited.
* By adding a new header (at the bottom of this header)
* with the word "Editor" on top of it.
*/

package cufy.concurrent;

import org.junit.Assert;
import org.junit.Test;

import java.util.concurrent.atomic.AtomicInteger;

@SuppressWarnings("JavaDoc")
public class LockTest {
@Test(timeout = 50)
public void lock_release_close() throws InterruptedException {
AtomicInteger integer = new AtomicInteger(0);
Lock<Object> lock = new Lock<>(integer);

Forever parallel = new Forever(loop -> {
synchronized (integer) {
integer.addAndGet(1);
}
});

parallel.thread();
parallel.pair();

lock.lock();

int current = integer.get();

Thread.sleep(5);

Assert.assertEquals("Lock not locked", current, integer.get());

lock.unlock();

Thread.sleep(5);

synchronized (integer) {
Assert.assertNotEquals("Lock not released", current, integer.get());
}

parallel.notify(Loop.BREAK);
parallel.join();
lock.close();

Assert.assertFalse("Lock not closed", lock.isAlive());

try {
lock.lock();
Assert.fail("Already closed");
} catch (Exception ignored) {
}
}

@Test(timeout = 50)
public void wrong_caller() throws InterruptedException {
Lock[] lock = new Lock[1];

Thread thread = new Thread(() -> lock[0] = new Lock<>());
thread.start();
thread.join();

try {
//noinspection CallToThreadRun
lock[0].run();
Assert.fail("Wrong caller test fail");
} catch (Exception ignored) {
}
try {
lock[0].lock();
Assert.fail("Wrong caller test fail");
} catch (Exception ignored) {
}
try {
lock[0].unlock();
Assert.fail("Wrong caller test fail");
} catch (Exception ignored) {
}
}
}

0 comments on commit 66873f8

Please sign in to comment.