java—在多个锁中,当第一个线程锁定到第一个任务时,如何使第二个线程不处于空闲状态,而是锁定到下一个任务?

kpbpu008  于 2021-06-26  发布在  Java
关注(0)|答案(2)|浏览(396)

处理器类-

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();
    }
}

主要方法-

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

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

在第一次运行时,thread-0或thread-1会锁定job1(),而另一个会空闲5秒钟。
在第一个线程释放job1()上的锁并锁定job2()之后,第二个线程将获得job1()上的锁。
我希望第二个线程不应该处于空闲状态,因为job1()被第一个线程锁定,所以它应该先锁定job2(),然后再锁定job1()。
怎么做?
注:这是基本方案。实际上,即使有100个任务和5个线程,我也希望我的代码能够工作。

2wnc66cl

2wnc66cl1#

我想,您正在寻找一种方法来“获取一个锁,如果可能,并做其他事情,如果它不是免费的”。
你可以用 ReentrantLock#tryLock .
方法:
只有在调用时没有被另一个线程持有时才获取锁。
它返回: true 如果锁是空闲的并且被当前线程获取,或者锁已经被当前线程持有;以及 false 否则
以下是问题代码的修改版本:
如果第一个任务的锁是空闲的,线程将运行第一个作业
否则它将在第二个之后运行它 Processor.java :

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

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

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

    public void run() {
        boolean executedFirst = false;
        if (lock1.tryLock()) {
            try {
                doJob1();
                executedFirst = true;
            } finally {
                lock1.unlock();
            }
        }
        try {
            lock2.lock();
            doJob2();
        } finally {
            lock2.unlock();
        }

        if (!executedFirst) {
            try {
                lock1.lock();
                doJob1();
            } finally {
                lock1.unlock();
            }
        }
    }

    public static void main(String[] args) {
        new Processor().start();
        new Processor().start();
    }
}

输出示例:

Thread-1 doing job2
Thread-0 doing job1
Thread-0 completed job1
Thread-1 completed job2
Thread-1 doing job1
Thread-0 doing job2
Thread-1 completed job1
Thread-0 completed job2

请注意 lock / tryLock 以及 unlock 调用被包围成 try / finally 进入篮子和球: Color.java :

public enum Color {
    RED,
    GREEN,
    BLUE;

    public static Color fromOrdinal(int i) {
        for (Color value : values()) {
            if (value.ordinal() == i) {
                return value;
            }
        }
        throw new IllegalStateException("Unknown ordinal = " + i);
    }
}
``` `Basket.java` :

@Data(staticConstructor = "of")
public class Basket {
private final Color color;
// balls that this basket has
private final List balls = new ArrayList<>();
private final Lock lock = new ReentrantLock();
}
`Ball.java`
@Value(staticConstructor = "of")
public class Ball {
Color color;
}
``` Boy.java 每个男孩挑一个球( queue.poll() )
跑到篮下( baskets.get(color) (颜色相同)
根据篮子被占时的行为,他:
将球扔掉,然后再试一次(可选) a (代码中)
等待篮子释放(选项 b )
请注意,使用选项 a 对某些人来说这是可能的 boy 当另一个扔掉一个 ball 而且还没有人捡起它(无论如何,会有人捡起它放进篮子里)

@RequiredArgsConstructor
public class Boy implements Runnable {
    private final Map<Color, Basket> baskets;
    private final Queue<Ball> balls;

    @Override
    public void run() {
        Ball ball;
        while ((ball = balls.poll()) != null) {
            Color color = ball.getColor();
            Basket basket = baskets.get(color);
            // a
            if (basket.getLock().tryLock()) {
                try {
                    basket.getBalls().add(ball);
                } finally {
                    basket.getLock().unlock();
                }
            } else {
                balls.offer(ball);
            }

            // b
            /*
            try {
                basket.getLock().lock();
                basket.getBalls().add(ball);
            } finally {
                basket.getLock().unlock();
            }
             */
        }
    }
}

最后呢 main :

Queue<Ball> balls = new LinkedBlockingQueue<>();
ThreadLocalRandom.current().ints(0, 3)
        .mapToObj(Color::fromOrdinal)
        .map(Ball::of)
        .limit(1000)
        .forEach(balls::add);
Map<Color, Basket> baskets = Map.of(
        Color.RED, Basket.of(Color.RED),
        Color.GREEN, Basket.of(Color.GREEN),
        Color.BLUE, Basket.of(Color.BLUE)
);
List<Thread> threads = IntStream.range(0, 100)
        .mapToObj(ignore -> new Boy(baskets, balls))
        .map(Thread::new)
        .collect(Collectors.toList());
threads.forEach(Thread::start);
for (Thread thread : threads) {
    thread.join();
}

baskets.forEach((color, basket) -> System.out.println("There are "
        + basket.getBalls().size() + " ball(-s) in " + color + " basket"));

输出示例:

There are 331 ball(-s) in GREEN basket
There are 330 ball(-s) in BLUE basket
There are 339 ball(-s) in RED basket
zed5wv10

zed5wv102#

下面是一个稍微复杂一点的示例,其中包含作业的对象和指示作业是否已执行的条件变量,以及 Package 器如何调整 ReentrantLock 一个try with resources语句。

/**
 * 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();
    }
}

几点注意事项
这个 run() 中的方法 Processor 现在概括为n个作业的列表。当任何作业尚未执行时,它将尝试执行它们,并在所有作业完成后完成。
这个 TryLocker 班级是 AutoCloseable ,所以锁定和解锁 Job 可以通过在try with resources语句中创建它的示例来完成。
这个 Locker 类在这里是未使用的,但它演示了如何对块执行相同的操作 lock() 打电话而不是打电话 tryLock() 打电话。 TryLocker 也可能需要一段时间来调用 tryLock 如果愿意的话,在放弃之前等待一段时间;这个修改留给读者作为练习。
这个 hasNotRun() 方法 Job 只是为了制造 anyMatch(Job::hasNotRun) 更具可读性 run() 方法 Processor ; 它可能没有发挥它的作用,可以不使用。
locker类不会使用 Objects.requireNonNull ; 他们通过调用一个方法来立即使用它,所以如果它为null,他们仍然会抛出一个npe,但是放置一个显式的requirennoull可能会使它们更清楚。
locker类不必检查是否已经解锁了锁 ReentrantLock 打电话之前 unlock() 使它们幂等;他们会扔一个 IllegalMonitorStateException 那样的话。在过去,我用一个flag变量对此进行了修改,以避免出现这种情况,但是由于我的意图是在try with resources语句中使用它们,该语句只调用 close() 方法一次,我认为如果有人手动调用close方法,最好让它们爆炸。

相关问题