Java 一次仅通过一个线程使用对象

Java 一次仅通过一个线程使用对象,java,multithreading,concurrency,Java,Multithreading,Concurrency,我有一本藏书(地图)。在多线程环境中,我需要map中myObject的实例一次只被一个线程使用。考虑下面的例子。 public class MyObject{ String name; public MyObject(String objName){ this.name = name; } public void doSomeTimeConsumingAction(){ Thread.sleep(10000);

我有一本藏书(地图)。在多线程环境中,我需要map中myObject的实例一次只被一个线程使用。考虑下面的例子。

public class MyObject{

     String name;
     public MyObject(String objName){
        this.name = name;
     }
     public void doSomeTimeConsumingAction(){
        Thread.sleep(10000);
     }
     public void doSomeOther(){
        //doSomething
     }
     public void doMany(){
        //doSomething
     }
}

public class ObjectUtil {

      public static Map<String, MyObject> map = new HashMap<>();
      static {
         map.put("a", new MyObject("a"));
         map.put("b", new MyObject("b"));
         map.put("c", new MyObject("c"));
      }
      
      public static getObject(String key){
           return map.get(key);
      }

}

public static void main(String[] args){

    Thread t1 = new Thread(new Runnable(){
      @Override
      public void run(){
         System.out.println("t1 starts");
         MyObject obj = ObjectUtil.getObject("a");
         obj.doSomeTimeConsumingAction();
         System.out.println("t1 ends");
      });
      Thread t2 = new Thread(new Runnable(){
      @Override
      public void run(){
         System.out.println("t2 starts");
         MyObject obj = ObjectUtil.getObject("a");
         obj.doSomeTimeConsumingAction();
         System.out.println("t2 ends");
      });
      Thread t3 = new Thread(new Runnable(){
      @Override
      public void run(){
         System.out.println("t3 starts");
         MyObject obj = ObjectUtil.getObject("b");
         obj.doSomeTimeConsumingAction();
         System.out.println("t3 ends");
      });

      t1.start();
      t2.start();
      t3.start();
}
解释-->在上面的示例中,线程t1和t2都尝试使用键“a”从映射访问同一个实例,而t3使用键“b”访问不同的实例。因此,t1、t2、t3同时启动。而t1、t3(或t2、t3)先结束,然后另一个结束

我不能使用synchronized on map或getObject()方法,因为这不会锁定要使用的对象实例

简单地说,我如何知道一个对象的实例是否被线程使用?如何防止其他线程访问同一实例?可能吗


编辑:更新了代码,我也无法同步对象中的doSomeTimeConsumingAction方法,因为线程可以访问其他方法。这与要访问的方法无关,整个实例一次只能由一个线程访问。如果要求太多,请原谅。

您希望通过
dosmetimeconsingaction
实现互斥,以便一次只能有一个线程运行对象的
dosmetimeconsingaction
方法

通过使
dosmometimeconsumingaction
同步,可以轻松实现这一点:

public class MyObject{

     String name;
     public MyObject(String objName){
        this.name = name;
     }
     public synchronized void doSomeTimeConsumingAction(){
        Thread.sleep(10000);
     }
}
或者,在线程本身中使用
synchronized
块,这将获得对象上的锁。这将保证尝试获取相同锁的线程之间的互斥

  public void run(){
     System.out.println("t2 starts");
     MyObject obj = ObjectUtil.getObject("a");
     synchronized (obj) {
         obj.doSomeTimeConsumingAction();
         // call other methods of obj if you want
     }
     System.out.println("t2 ends");
  });

除非您知道每个调用方只调用一个方法,否则同步所有方法是不正确的。否则,调用可能会交错,这通常不是一个好主意。如果有疑问,调用方应该在处理整个实例之前对其进行同步

如果你不相信(所有)你的呼叫者会遵守这条规则-这里有一个简单的方法“锁定”一个
MyObject

publicstaticmap=newconcurrenthashmap();
公共静态无效句柄(字符串键、使用者句柄){
map.computefpresent(键,o->{
handler.apply(o);//对于任何给定的“o”,一次只能有一个线程在这里
返回o;
});
}
...
handle(“a”,o->o.doSomeTimeConsumingAction());

虽然短而安全,但它并不是最好的线程性能(CHM也可能阻止访问其他
MyObject
s)。

这是使用
同步
块的教科书示例。如果要对对象进行互斥,可以轻松地将
doSomeTimeConsumingAction()
方法声明为
synchronized

public synchronized void doSomeTimeConsumingAction() throws InterruptedException {
    Thread.sleep(10000);
}
即使您将该方法标记为“已同步”,实际锁定也会应用于对象而不是方法。锁只能应用于像对象这样的实时实体,而方法只是逻辑块

Java的设计方式使您能够控制方法的线程安全性,而不是在类级别锁定整个对象。因此,由您决定哪些方法应该同步,哪些不应该同步


我理解您在使用同步块时面临的困境,我建议您阅读更多关于同步块的内容并使用它以获得更舒适的体验。

您可以同步
doSomeTimeConsumingOperation
@BurakSerdar我不能这样做,因为我需要整个对象只使用一次。请更新代码。希望能有所帮助。谢谢好吧,你可以让所有的方法同步。也许你应该通过解释你的实际用例来扩展这个问题,因为它在这样呈现时看起来很琐碎。你可以在每次访问MyObject实例时使用
synchronized(obj)
块。更新了我的问题。请务必帮助我。“这与要访问的方法无关,一次只能由一个线程访问整个实例”-然后使所有方法同步,或者使用第二种方法(请参阅更新),如果我使所有方法同步,它不是使同一实例的两个不同方法同时在两个线程中可用吗?从而使同一实例可用于两个线程?我不确定方法中的同步在实例级别如何工作。不,
synchronized
会锁定对象,而不是方法。当另一个线程对同一对象运行另一个同步方法时,该线程无法输入同步方法。
public synchronized void doSomeTimeConsumingAction() throws InterruptedException {
    Thread.sleep(10000);
}