java 如何使线程在调用当前被阻塞的synchronized方法时跳到下一行代码?

0pizxfdo  于 2023-01-15  发布在  Java
关注(0)|答案(1)|浏览(156)
    • 设想**

多个线程向ArrayList * list * 添加对象,该列表是ListHolder对象 * listHolder * 的属性,所有线程共享该属性,线程调用ListHolder.addObject()向 * list * 添加对象,addObject()方法包含一个 * synchronized**块 *,它将 * list * 作为 * 监视对象 * 传递,以防止多线程错误。(以独立且异步的方式)每个线程将调用方法ListHolder.processList(),该方法对 * list * 执行操作并使其为空。
如果processList()是一个 * synchronized * 方法,当它正在使用时,试图调用它的线程将等待,直到阻塞线程完成,然后再调用它。

    • 问题**

但是,如何使调用processList()的线程在被另一个线程使用(阻塞)时等待,而是移动到下一行代码?
这将大大提高效率,因为一旦被阻塞的线程完成,它将尝试处理一个空的 * list *,而它可以构造要添加到 * list * 的对象,而不是等待。

wlwcrazw

wlwcrazw1#

我能想到至少两个可能的解决办法。
1.使用Semaphore
1.处理列表的副本而不持有锁。

信号量

你可以创建一个Semaphore,它有一个名为tryAcquire()的方法,试图立即获得一个许可;如果有一个可用,则返回true,否则返回false
例如:

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.concurrent.Semaphore;
  4. public class ListHolder {
  5. private final Semaphore processPermit = new Semaphore(1);
  6. private final List<Foo> list = new ArrayList<>();
  7. public void addObject(Foo object) {
  8. synchronized (list) {
  9. list.add(object);
  10. }
  11. }
  12. public void processList() {
  13. if (processPermit.tryAcquire()) {
  14. try {
  15. synchronized (list) {
  16. // process list...
  17. }
  18. } finally {
  19. processPermit.release();
  20. }
  21. }
  22. }
  23. }

这将只允许一个线程在任何给定的时间处理列表,如果一个线程已经在处理列表,那么另一个线程将直接从processList()返回,而不做任何事情。
这种方法的缺点是,当一个线程处理列表时,没有其他线程可以添加到列表中,这可能会给生产者带来瓶颈。

处理副本

如果您的方案允许,您可以改为复制列表,清除列表,然后在不持有锁的情况下处理副本。您将仅在创建副本时同步列表。
例如:

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class ListHolder {
  4. private final List<Foo> list = new ArrayList<>();
  5. public void addObject(Foo object) {
  6. synchronized (list) {
  7. list.add(object);
  8. }
  9. }
  10. public void processList() {
  11. List<Foo> copy = null;
  12. synchronized (list) {
  13. if (!list.isEmpty()) {
  14. copy = new ArrayList<>(list);
  15. list.clear();
  16. }
  17. }
  18. if (copy != null) {
  19. // do processing...
  20. }
  21. }
  22. }

复制列表应该只需要相对于处理很短的时间,假设处理是昂贵的,这样线程就不会持有锁太长时间。
这种方法的优点是多个线程可以同时处理 * 不同的数据集 *,同时仍然允许其他线程相对不受阻碍地构造和添加对象到列表中。但是,如果由于您的问题中没有说明的原因,您需要在整个处理操作期间持有锁,或者如果两个线程不能同时处理元素,则这种方法是不可行的。

额外好处:混合解决方案

你也可以考虑以上两种解决方案的混合,使用Semaphore,这样在任何给定的时间只有一个线程可以处理列表,但是处理一个副本,这样其他线程可以继续向列表添加对象。
例如:

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.concurrent.Semaphore;
  4. public class ListHolder {
  5. private final Semaphore processPermit = new Semaphore(1);
  6. private final List<Foo> list = new ArrayList<>();
  7. public void addObject(Foo object) {
  8. synchronized (list) {
  9. list.add(object);
  10. }
  11. }
  12. public void processList() {
  13. if (processPermit.tryAcquire()) {
  14. try {
  15. List<Foo> copy;
  16. synchronized (list) {
  17. copy = new ArrayList<>(list);
  18. list.clear();
  19. }
  20. processList(copy);
  21. } finally {
  22. processPermit.release();
  23. }
  24. }
  25. }
  26. private void processList(List<Foo> copy) {
  27. // do processing...
  28. }
  29. }
展开查看全部

相关问题