Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
263 views
in Technique[技术] by (71.8m points)

java - In multiple locks when first thread has locked on to first task, how to make second thread to not sit idle and instead lock on to next task?

Processor class -

public class Processor extends Thread {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    private void doJob1() {
        synchronized (lock1) {
            System.out.println(Thread.currentThread().getName() + " doing job1");
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + " completed job1");
        }
    }

    private void doJob2() {
        synchronized (lock2) {
            System.out.println(Thread.currentThread().getName() + " doing job2");
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + " completed job2");
        }
    }

    public void run() {
        doJob1();
        doJob2();
    }
}

Main method -

    final Processor processor1 = new Processor();
    final Processor processor2 = new Processor();

    processor1.start();
    processor2.start();

Here on first run either Thread-0 or Thread-1 takes a lock of job1() and the other sits idle for 5 seconds.

After the first thread releases the lock on job1() and locks job2(), the second thread takes a lock on job1().

I want that the second thread should not sit idle and since job1() is locked by first thread, it should instead lock job2() and then lock job1().

How to do so?

Note: This is the basic blue print. In reality I want my code to work even if there are a 100 tasks and 5 threads.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Here is a slightly more complicated example with an object for the job and the condition variable that indicates whether the job has been performed, and examples of how wrappers can adapt a ReentrantLock to a try-with-resources statement.

/**
 * A Job represents a unit of work that needs to be performed once and
 * depends upon a lock which it must hold while the work is performed.
 */
public class Job {
    private final Runnable job;
    private final ReentrantLock lock;
    private boolean hasRun;

    public Job(Runnable job, ReentrantLock lock) {
        this.job = Objects.requireNonNull(job);
        this.lock = Objects.requireNonNull(lock);
        this.hasRun = false;
    }

    /**
     * @returns true if the job has already been run
     */
    public boolean hasRun() {
        return hasRun;
    }

    // this is just to make the test in Processor more readable
    public boolean hasNotRun() {
        return !hasRun;
    }

    /**
     * Tries to perform the job, returning immediately if the job has
     * already been performed or the lock cannot be obtained.
     *
     * @returns true if the job was performed on this invocation
     */
    public boolean tryPerform() {
        if (hasRun) {
            return false;
        }
        try (TryLocker locker = new TryLocker(lock)) {
            if (locker.isLocked()) {
                job.run();
                hasRun = true;
            }
        }
        return hasRun;
    }
}

/**
 * A Locker is an AutoCloseable wrapper around a ReentrantLock.
 */
public class Locker implements AutoCloseable {
    private final ReentrantLock lock;

    public Locker(final ReentrantLock lock) {
        this.lock = lock;
        lock.lock();
    }

    @Override
    public void close() {
        lock.unlock();
    }
}

/**
 * A TryLocker is an AutoCloseable wrapper around a ReentrantLock that calls
 * its tryLock() method and provides a way to test whether than succeeded.
 */
public class TryLocker implements AutoCloseable {
    private final ReentrantLock lock;

    public TryLocker(final ReentrantLock lock) {
        this.lock = lock.tryLock() ?  lock : null;
    }

    public boolean isLocked() {
        return lock != null;
    }

    @Override
    public void close() {
        if (isLocked()) {
            lock.unlock();
        }
    }
}

/**
 * A modified version of the Processor class from the question.
 */
public class Processor extends Thread {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    private void snooze(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void doJob1() {
        System.out.println(Thread.currentThread().getName() + " doing job1");
        snooze(5000);
        System.out.println(Thread.currentThread().getName() + " completed job1");
    }

    private void doJob2() {
        System.out.println(Thread.currentThread().getName() + " doing job2");
        snooze(5000);
        System.out.println(Thread.currentThread().getName() + " completed job2");
    }

    public void run() {
        Job job1 = new Job(() -> doJob1(), lock1);
        Job job2 = new Job(() -> doJob2(), lock2);

        List<Job> jobs = List.of(job1, job2);

        while (jobs.stream().anyMatch(Job::hasNotRun)) {
            jobs.forEach(Job::tryPerform);
        }
    }

    public static void main(String[] args) {
        final Processor processor1 = new Processor();
        final Processor processor2 = new Processor();

        processor1.start();
        processor2.start();
    }
}

A few notes:

  • The run() method in Processor now generalizes to a list of n jobs. While any of the jobs have not been performed, it will try to perform them, completing after all jobs are done.
  • The TryLocker class is AutoCloseable, so locking and unlocking in Job can be done by creating an instance of it in a try-with-resources statement.
  • The Locker class is unused here but demonstrates how the same thing could be done for a blocking lock() call instead of a tryLock() call.
  • TryLocker could also take a time period and call the overload of tryLock that waits for up to an amount of time before giving up, if desired; that modification is left as an exercise for the reader.
  • The hasNotRun() method of Job is just there to make anyMatch(Job::hasNotRun) a little more readable in the run() method of Processor; it probably isn't pulling its weight, and could be dispensed with.
  • The locker classes don't check that the lock passed in is not null using Objects.requireNonNull; they use it right away by calling a method on it, so if it's null they will still throw an NPE, but putting an explicit requireNonNull might make them clearer.
  • The locker classes don't bother to check if they've already unlocked the ReentrantLock before calling unlock() to make them idempotent; they will throw an IllegalMonitorStateException in that case. In the past I wrote a variation on this with a flag variable to avoid this, but since the intention is to use them in a try-with-resources statement, which will only call the close() method once, I think it's better to let them blow up if someone manually calls the close method.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...