Java Language Specification 和 How to make a dead lock using it 中 synchronized 中的 synchronized 是什么意思?

What does synchronized in synchronized mean from Java Language Specification and How to make a dead lock using it?

我正在阅读 Java 语言规范,其中有一个这样的例子:

class Test {
    public static void main(String[] args) {
        Test t = new Test();
        synchronized(t) {
            synchronized(t) {
                System.out.println("made it!");
            }
        }
    }
}

你可以从 http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.19

有一个注释

Note that this program would deadlock if a single thread were not permitted to lock a monitor more than once.

注释的确切含义是什么?如何使用它来制造死锁?

你不能那样死锁,因为你只有一个线程。

class DeadLock {
   public synchronized void foo(int i) {
    System.out.println("Processing :" + i );
    if(i == 0) while(true){
       try {
        Thread.sleep(1000);                 //1000 milliseconds is one second.
       } catch(InterruptedException ex) {
         Thread.currentThread().interrupt();
       }
    };
    System.out.println("Finished:" + i );
   }
}

在你的主要

DeadLock dl = new DeadLock();

Integer[] intArray = {0, 1, 2, 3, 4, 5, 6, 7, 8 };
List<Integer> listOfIntegers =
   new ArrayList<>(Arrays.asList(intArray));
listOfIntegers.stream().parallelStream().forEach(i -> dl.foo(i));

我没有测试过,但是当 foo(0) 启动时,它应该会阻止其他人启动。

示例表明synchronized是一个reentrant锁。

synchronized(t) 块只允许一个线程在其中,(以及程序中的任何其他 synchronized(t) 块)。

由于同步是可重入的,synchronized(t) 块中的线程可以进入另一个 synchronized(t) 块,尽管 synchronized(t) 块中已经有一个线程 - 该线程本身。

如果锁不是可重入的,第二个 synchronized(t) 会造成死锁,因为该线程在再次锁定之前必须放弃锁。

您不能使用此代码创建死锁。文本解释了为什么规则是这样的。规则是一个线程不能多次锁定同一个监视器。如果线程 可以 多次锁定同一个监视器,则会发生以下情况:

public static void main(String[] args) {
    Test t = new Test();
    synchronized(t) {
        synchronized(t) {
            System.out.println("made it!");
        }
    }
}

第一个 synchronized 会导致线程锁定 t。当程序到达第二个 synchronized 时,线程将尝试锁定 t,但是由于 t 已经被锁定(由线程本身),线程将永远坐在那里等待本身解锁 t,但线程永远不会解锁 t,因为必须等待自身解锁 t,然后才能解锁 t.

如果规则不同,事情就会是这样。但既然规则就是这样,就不会有僵局。

请注意,实际上没有人会编写这样的代码。但是用方法做同样的事情可能很常见。假设你有两个 public 方法,它们都需要锁定一些东西:

public class C {

    SomeObject monitor;

    public void method1() {
        synchronized(monitor) {
            ...stuff...
        }
    }

    public void method2() {
        synchronized(monitor) {
            ...stuff
            method1();
            ...stuff
        }
    }
}

如果不存在关于不锁定多次的规则,这将在 method2 调用 method1 时出现死锁。由于存在规则,因此在这种情况下您不必担心死锁。

Java 中的同步块是可重入的。这意味着,如果一个 Java 线程进入一个同步代码块,从而锁定该块同步的监视器对象,该线程可以进入同步的其他 Java 代码块监控对象。

public class Reentrant{

  public synchronized outer(){
    inner();
  }

  public synchronized inner(){
    //do something
  }
}

outer() 和 inner() 都声明为同步的,在 Java 中相当于一个 synchronized(this) 块。如果线程调用 outer(),则从 outer() 内部调用 inner() 没有问题,因为两个方法(或块)在同一个监视器对象 ("this") 上同步。如果线程已经持有监视器对象上的锁,则它可以访问在同一监视器对象上同步的所有块。这称为重入。线程可以重新进入它已经持有锁的任何代码块。

但是如果你有自己的自定义锁 class 那么你可以在同一监视器上的嵌套锁定中创建死锁,如果它不能重入的话。

public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}

public class Reentrant2{

  Lock lock = new Lock();

  public outer(){
    lock.lock();
    inner();
    lock.unlock();
  }

  public synchronized inner(){
    lock.lock();
    //do something
    lock.unlock();
  }
}

调用outer() 的线程将首先锁定Lock 实例。然后它将调用 inner()。在 inner() 方法中,线程将再次尝试锁定 Lock 实例。这将失败(意味着线程将被阻塞),因为 Lock 实例已经在 outer() 方法中被锁定。

为了使锁 class 可重入,我们需要做一个小改动:

public class Lock{

  boolean isLocked = false;
  Thread  lockedBy = null;
  int     lockedCount = 0;

  public synchronized void lock()
  throws InterruptedException{
    Thread callingThread = Thread.currentThread();
    while(isLocked && lockedBy != callingThread){
      wait();
    }
    isLocked = true;
    lockedCount++;
    lockedBy = callingThread;
  }


  public synchronized void unlock(){
    if(Thread.curentThread() == this.lockedBy){
      lockedCount--;

      if(lockedCount == 0){
        isLocked = false;
        notify();
      }
    }
  }

  ...
}

注意 while 循环(自旋锁)现在还考虑了锁定 Lock 实例的线程。如果锁被解锁(isLocked = false)或者调用线程是锁定Lock实例的线程,while循环将不会执行,调用lock()的线程将被允许退出方法。

另外,我们需要统计锁被同一个线程加锁的次数。否则,单次调用 unlock() 将解锁锁,即使锁已被多次锁定。我们不希望锁被解锁,直到锁定它的线程执行了与 lock() 调用相同数量的 unlock() 调用。

锁 class 现在是 ReentrantLock。

在 java.util.concurrent 包中你有 ReentrantLock 可用于重入。

检查此 link 阅读更多相关信息:http://tutorials.jenkov.com/java-concurrency/locks.html