使用对象一次只能由一个线程使用

huangapple go评论76阅读模式
英文:

Using an Object by only one thread at a time

问题

我有一个集合(Map<String, MyObject>)。在多线程环境中,我需要映射中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 starts
t2 starts
t3 starts
/* 等待 10 秒 */
t1 ends
t3 ends
/* 等待 10 秒 */
t2 ends

解释:在上面的代码中,线程t1和t2都尝试访问具有键"a"的映射中的同一个实例,而t3访问具有键"b"的不同实例。因此,t1、t2和t3同时启动。t1、t3(或t2、t3)先结束,然后另一个线程结束。

我不能在映射上使用synchronized,也不能在getObject()方法上使用,因为这样不会锁定要使用的对象实例。

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

编辑:已更新代码,我也不能在对象中同步doSomeTimeConsumingAction方法,因为那样其他方法就可能被线程访问。不是关于方法的访问,整个实例应一次只能被一个线程访问。如果这样的要求过于苛刻,请原谅我。

英文:

I have a collection ( Map<String, MyObject> ). In a multi-threaded environment, I need the instance of myObject in map to be used by only one thread at once. Consider the below example.

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&lt;String, MyObject&gt; map = new HashMap&lt;&gt;();
static {
map.put(&quot;a&quot;, new MyObject(&quot;a&quot;));
map.put(&quot;b&quot;, new MyObject(&quot;b&quot;));
map.put(&quot;c&quot;, new MyObject(&quot;c&quot;));
}
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(&quot;t1 starts&quot;);
MyObject obj = ObjectUtil.getObject(&quot;a&quot;);
obj.doSomeTimeConsumingAction();
System.out.println(&quot;t1 ends&quot;);
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run(){
System.out.println(&quot;t2 starts&quot;);
MyObject obj = ObjectUtil.getObject(&quot;a&quot;);
obj.doSomeTimeConsumingAction();
System.out.println(&quot;t2 ends&quot;);
});
Thread t3 = new Thread(new Runnable(){
@Override
public void run(){
System.out.println(&quot;t3 starts&quot;);
MyObject obj = ObjectUtil.getObject(&quot;b&quot;);
obj.doSomeTimeConsumingAction();
System.out.println(&quot;t3 ends&quot;);
});
t1.start();
t2.start();
t3.start();
}

My expected Output

t1 starts
t2 starts
t3 starts
/* wait 10 sec */
t1 ends
t3 ends
/* wait 10 sec */
t2 ends

Explanation --> In the above, threads t1 and t2 both try to access the same instance from the map with the key "a", while t3 accesses different instance with key "b".So, t1, t2, t3 starts concurrently . while t1, t3 (or t2, t3) ends first, then the other one ends.

I cannot use synchronized on map or getObject() method, coz that would not lock the object instance to be used.

Simply put, How can I know if an instance of an object is used by a thread? and, How to prevent other thread from accessing the same instance? Is it possible?

Edit: Updated the code , I cannot synchronize the method doSomeTimeConsumingAction in the object as well, coz then the other methods could br accessed by the thread.It is not about the methods to access, the whole instance should be accessed only by one thread at a time. Forgive me if it is too much to ask.

答案1

得分: 1

你想要通过doSomeTimeConsumingAction实现互斥,以便一次只能有一个线程在对象的doSomeTimeConsumingAction方法上运行。

通过将doSomeTimeConsumingAction设为同步方法,可以轻松实现:

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();
       // 调用对象的其他方法(如果需要的话)
   }
   System.out.println("t2 ends");
}
英文:

You want to achieve mutual exclusion with doSomeTimeConsumingAction, so that only one thread at a time can be running the doSomeTimeConsumingAction method of an object at a time.

That's easily achieved by making doSomeTimeConsumingAction synchronized:

public class MyObject{
String name;
public MyObject(String objName){
this.name = name;
}
public synchronized void doSomeTimeConsumingAction(){
Thread.sleep(10000);
}
}

Alternatively, use a synchronized block in the threads themselves, which acquires a lock on the object. This will guarantee mutual exclusion among threads that try to acquire the same lock.

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

答案2

得分: 1

同步所有方法并不是一个好的做法,除非你知道每个调用者只会调用单个方法。否则,调用可能会交错,这通常不是一个好主意。如果不确定,调用者在操作实例之前应该同步整个实例。

如果你不相信(所有的)调用者会遵循那个规则 - 这里有一种简单的方法来“锁定”一个 MyObject

    public static Map<String, MyObject> map = new ConcurrentHashMap<>();

    public static void handle(String key, Consumer<MyObject> handle) {
      map.computeIfPresent(key, o -> {
        handler.apply(o); // 任何给定的“o”一次只能有一个线程在这里
        return o;
      });
    }
    ...
    ObjectUtil.handle("a", o -> o.doSomeTimeConsumingAction());

虽然简短且安全,但它并不是最佳的线程性能(CHM 也可能阻塞对其他 MyObject的访问)。

英文:

Synchronizing all methods is not OK unless you know each caller would only ever invoke a single method. Otherwise calls can interleave which is usually a bad idea. If in doubt callers should synchronize the whole instance before working on it.

If you don't trust (all) your callers to follow that rule - here's a simple way of "locking" a MyObject.

    public static Map&lt;String, MyObject&gt; map = new ConcurrentHashMap&lt;&gt;();

    public static void handle(String key, Consumer&lt;MyObject&gt; handle) {
      map.computeIfPresent(key, o -&gt; {
        handler.apply(o); // Only one thread can be here at a time for any given &quot;o&quot;
        return o;
      });
    }
    ...
    ObjectUtil.handle(&quot;a&quot;, o -&gt; o.doSomeTimeConsumingAction());

Whilst short and safe, it is not the greatest threading performance (CHM may also block access to other MyObjects).

答案3

得分: 1

这是一个使用synchronized块的教科书般例子。如果您想要为对象实现互斥,您可以将doSomeTimeConsumingAction()方法声明为synchronized

public synchronized void doSomeTimeConsumingAction() throws InterruptedException {
    Thread.sleep(10000);
}

尽管您将方法标记为synchronized,但实际的锁定是应用于对象而不是方法。锁定只能应用于实时实体,如对象,而方法只是逻辑块。

Java被设计成让您有能力使方法线程安全,而不是在类级别上锁定整个对象。因此,您可以自行决定哪些方法应该是synchronized,哪些方法不应该是synchronized

我理解您在使用synchronized块方面的困惑,建议您深入阅读并进行实践,以便更加熟悉这一概念。

英文:

This is a textbook example of using synchronized blocks. If you want to have mutual exclusion for an object, you can comfortably declare the doSomeTimeConsumingAction() method as synchronized.

public synchronized void doSomeTimeConsumingAction() throws InterruptedException {
Thread.sleep(10000);
}

Even though you mark the method to be synchronized the actual lock is applied on the object and not on the method. Locks can only be applied to real-time entities like objects, and methods are just logic blocks.

Java is designed in such a way that it puts you in control to make the methods thread-safe, rather than locking the whole object at the class level. So, it's up to you to decide which methods should be synchronized and which shouldn't be.

I understand your dilemma in using synchronized blocks and I recommend you to read more on that and play with it to get more comfortable.

huangapple
  • 本文由 发表于 2020年8月24日 22:29:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/63563067.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定