Draymonders / Code-Life

The marathon continues though.
27 stars 3 forks source link

Synchronized 可重入问题 #91

Open Draymonders opened 3 years ago

Draymonders commented 3 years ago

很久没用多线程了,包括多线程也没遇到那种资源互斥的情况,所以对的知识也就渐渐淡忘了。

今天阅读RedisLettuce的源码,发现sync可重入。刚开始还有点儿怀疑,便自己写了一个demo

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class SyncRetry {

  private final Object monitor = new Object();

  private Object connection = new Object();

  private synchronized void validateConnection(int id) {
    System.out.println("id: " + id + " enter");
    synchronized (this.monitor) {
      System.out.println("id: " + id + " try to reset connection");
      if (this.connection != null) {
        resetConnection(id);
      }
    }
  }

  private synchronized void resetConnection(int id) {
    synchronized (this.monitor) {
      if (this.connection != null) {
        this.connection = null;
        System.out.println("id: " + id + " reset the connection");
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    List<Thread> threads = new ArrayList<>();
    SyncRetry syncRetry = new SyncRetry();
    IntStream.range(0, 5).forEach(id -> {
      threads.add(new Thread(() -> {
        syncRetry.validateConnection(id);
      }));
    });
    for (Thread thread : threads) {
      thread.start();
    }
    for (Thread thread : threads) {
      thread.join();
    }
  }
}

执行结果

id: 0 enter
id: 0 try to reset connection
id: 0 reset the connection
id: 4 enter
id: 4 try to reset connection
id: 2 enter
id: 2 try to reset connection
id: 3 enter
id: 3 try to reset connection
id: 1 enter
id: 1 try to reset connection
Draymonders commented 3 years ago

由此也明晰了,占有锁的是,线程对象,

public class SyncRetry {

  private final Object monitor = new Object();

  private Object connection = new Object();

  private synchronized void validateConnection(int id) {
    System.out.println("id: " + id + " enter");
    synchronized (this.monitor) {
      System.out.println("id: " + id + " try to reset connection");
      if (this.connection != null) {
        resetConnection(id);
      }
    }
  }

  private synchronized void resetConnection(int id) {
    synchronized (this.monitor) {
      if (this.connection != null) {
        this.connection = null;
        System.out.println("id: " + id + " reset the connection");
      }
    }
  }

  private synchronized void loopEnter(int id, int cnt) {
    if (cnt > 10) {
      return;
    }
    System.out.println("id: " + id + " loop cnt: " + cnt);
    loopEnter(id, cnt + 1);
  }

  public static void main(String[] args) throws InterruptedException {
    List<Thread> threads = new ArrayList<>();
    SyncRetry syncRetry = new SyncRetry();
    IntStream.range(0, 5).forEach(id -> {
      threads.add(new Thread(() -> {
        // syncRetry.validateConnection(id);
        syncRetry.loopEnter(id, 0);
      }));
    });
    for (Thread thread : threads) {
      thread.start();
    }
    for (Thread thread : threads) {
      thread.join();
    }
  }
}

执行结果

id: 0 loop cnt: 0
id: 0 loop cnt: 1
id: 0 loop cnt: 2
id: 0 loop cnt: 3
id: 0 loop cnt: 4
id: 0 loop cnt: 5
id: 0 loop cnt: 6
id: 0 loop cnt: 7
id: 0 loop cnt: 8
id: 0 loop cnt: 9
id: 0 loop cnt: 10
id: 4 loop cnt: 0
id: 4 loop cnt: 1
id: 4 loop cnt: 2
id: 4 loop cnt: 3
id: 4 loop cnt: 4
id: 4 loop cnt: 5
id: 4 loop cnt: 6
id: 4 loop cnt: 7
id: 4 loop cnt: 8
id: 4 loop cnt: 9
id: 4 loop cnt: 10
id: 3 loop cnt: 0
id: 3 loop cnt: 1
id: 3 loop cnt: 2
id: 3 loop cnt: 3
id: 3 loop cnt: 4
id: 3 loop cnt: 5
id: 3 loop cnt: 6
id: 3 loop cnt: 7
id: 3 loop cnt: 8
id: 3 loop cnt: 9
id: 3 loop cnt: 10
id: 2 loop cnt: 0
id: 2 loop cnt: 1
id: 2 loop cnt: 2
id: 2 loop cnt: 3
id: 2 loop cnt: 4
id: 2 loop cnt: 5
id: 2 loop cnt: 6
id: 2 loop cnt: 7
id: 2 loop cnt: 8
id: 2 loop cnt: 9
id: 2 loop cnt: 10
id: 1 loop cnt: 0
id: 1 loop cnt: 1
id: 1 loop cnt: 2
id: 1 loop cnt: 3
id: 1 loop cnt: 4
id: 1 loop cnt: 5
id: 1 loop cnt: 6
id: 1 loop cnt: 7
id: 1 loop cnt: 8
id: 1 loop cnt: 9
id: 1 loop cnt: 10

亚权总结: synchronize的实现原理是有一个lock对象,保留有线程的id 和锁的status,通过判断status 和id。

同样的线程再次sync时,先check lock对象的id,若同,则status 加1。

Draymonders commented 3 years ago

注意由于synchronized是基于monitor实现的,因此每次重入,monitor中的计数器仍会加1。