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

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

多个线程向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
例如:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

public class ListHolder {
    
  private final Semaphore processPermit = new Semaphore(1);
  private final List<Foo> list = new ArrayList<>();

  public void addObject(Foo object) {
    synchronized (list) {
      list.add(object);
    }
  }

  public void processList() {
    if (processPermit.tryAcquire()) {
      try {
        synchronized (list) {
          // process list...
        }
      } finally {
        processPermit.release();
      }
    }
  }
}

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

处理副本

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

import java.util.ArrayList;
import java.util.List;

public class ListHolder {
    
  private final List<Foo> list = new ArrayList<>();

  public void addObject(Foo object) {
    synchronized (list) {
      list.add(object);
    }
  }

  public void processList() {
    List<Foo> copy = null;

    synchronized (list) {
      if (!list.isEmpty()) {
        copy = new ArrayList<>(list);
        list.clear();
      }
    }

    if (copy != null) {
      // do processing...
    }
  }
}

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

额外好处:混合解决方案

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

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

public class ListHolder {
    
  private final Semaphore processPermit = new Semaphore(1);
  private final List<Foo> list = new ArrayList<>();

  public void addObject(Foo object) {
    synchronized (list) {
      list.add(object);
    }
  }

  public void processList() {
    if (processPermit.tryAcquire()) {
      try {
        List<Foo> copy;
        synchronized (list) {
          copy = new ArrayList<>(list);
          list.clear();
        }
        processList(copy);
      } finally {
        processPermit.release();
      }
    }
  }

  private void processList(List<Foo> copy) {
    // do processing...
  }
}

相关问题