Java多线程问题

6
假设有以下类:
public class TestObject{
    public void synchronized method1(){
        //some 1000 lines of code
    }

    public void method2(){
        //some 1000 lines of code
    }
}

假设有两个线程正在访问TestObject类的同一实例,我们称它们为t1和t2。我想知道以下情况会发生什么。
  1. t1正在访问method1()的中途。现在t2正在尝试访问method2()。
  2. t1正在访问method2()的中途。现在t2正在尝试访问method1()。
我的理解是,在第一个问题中,由于对象被t1锁定,线程t2将不会获得许可。在第二个问题中,线程t2将被授予访问权限并锁定对象,并使t1无法执行。但我的假设是错误的。有人能解释一下吗?
谢谢
6个回答

8

只有使用关键字 synchronized 的方法在线程运行时才会持有此对象的锁。
如果方法1和方法2都声明为synchronized,即使它们尝试运行不同的方法,一个线程也会阻塞另一个线程。
在您的示例中,仅有1个方法通过隐式锁进行了阻塞。
因此,t1和t2可以同时在方法1和方法2中运行(反之亦然)。
只有在尝试访问方法1时,t1或t2才会在锁已被获取的情况下阻塞。


4
当您声明一个方法为同步方法时,例如:
public synchronized void foo() {
    // Do something
}

编译器会将其视为您编写了以下内容:
public void foo() {
    synchronized (this) {
        // Do something
    }
}

在你的示例中,你有一个同步方法和一个非同步方法。这意味着只有对method1的访问将被锁定。锁定检查仅在进入synchronized块时进行,因此调用method2将不会触发任何锁定。
回答你的两个问题,无论哪种情况,两个线程都将被允许继续执行,因为它们不会尝试在同一个对象上获取锁。如果你声明method2为同步方法(或手动添加synchronized(this)块),那么一个线程将被强制等待另一个线程。
请记住:在对象上同步并不能防止其他线程调用该对象的方法。它只会阻止另一个线程使用相同的锁对象进入同步块。
顺便说一下,通常最好拥有一个内部锁对象,而不是声明方法为同步方法,例如:
class Foo {
    private final Object LOCK = new Object();
    public void doSomething() {
        synchronized (LOCK) {
            // Whatever
        }
    }
}

否则,我这样做可能会破坏你的线程安全:
class MessEverythingUp {
    public MessEverythingUp(Foo foo) {
        synchronized (foo) {
            while (true) {
                System.out.println("Pwnd ur thread safety");
            }
        }
    }
}

由于我锁定了foo的实例,你的同步方法(带有隐式的“synchronized (this)”)将无法获得锁定并将永久阻塞。最重要的是,你无法防止这种情况发生,因为我可以在任何对象上同步。显然,这个例子是极端的,但如果你不小心处理这种情况,可能会出现令人讨厌的、微妙的死锁错误。


3

在这两种情况下,第二个线程都将被允许执行其方法。

由于这两种方法中只有一个包含synchronized关键字,因此这两种方法可以同时执行。限制在于,在任何给定时间只能执行具有该关键字的一个方法,因为执行该方法需要对象上的锁。没有关键字的线程不需要锁定,并且始终允许执行,无论对象是否被锁定。

我还假设在method2的1000行代码中没有像这样的块:

synchronized (this) {
}

如果有,那么结果会有所不同。

  1. t2将被允许开始执行该方法。当它到达synchronized行时,它将等待t1完成method1并释放其锁定后才继续。
  2. t2将被允许开始执行该方法,只要t1不在synchronized代码块内。如果是,则t2将等待t1退出该块并释放锁定后才开始。如果t1尚未进入synchronized块,则t2将获取锁定,并且一旦t2完成该方法并释放锁定,t1将在到达同步代码块时等待t2完成并释放锁定。

2

一个方法是同步的,而另一个方法则不是。因此,无论对象上的锁(在这种情况下是方法所属的实例)是否已被获取,非同步方法都将不受阻碍地执行(因为它不会尝试获取或等待锁)。这意味着在两种情况下,两个线程都将运行而不互相等待 - 导致对象可能处于不一致的状态。


你应该确定,因为你是正确的 - 除非没有理由假设对象存在“可能不一致的状态”,因为你不知道方法做什么。 - Erick Robertson
1
@Erick Robertson:因此,我使用了“可能”这个词: )。谢谢,我现在将删除“不确定”的部分。 - MAK

0

method1()是同步的,因此被称为线程安全方法。当多个线程同时尝试访问此方法时,只有实例对象上的锁才能起作用。

method2()不是同步的,因此是一个线程不安全的方法,其他线程可以调用此方法,即使某个其他线程已经锁定了该实例,这就是为什么该方法被称为线程不安全方法。

在两种情况下,您提到一个线程将通过调用method1()获取实例上的锁,另一个线程将尝试访问不安全的method2(),因此两个线程都将执行。

此致
Tushar Joshi,那格浦尔


一个方法是否是线程安全的并不取决于synchronized关键字,而是取决于方法的编写方式。如果一个方法没有使用该关键字,并且没有以线程不安全的方式访问任何属性,则它完全可以是线程安全的。 - Erick Robertson
我同意@Erick的看法,如果方法被小心编写且不使用任何属性,则仍然可以是线程安全的。但是,当一个方法被定义为同步的时候,它总是变得线程安全的。根据我的经验,开发人员通常会编写一个没有同步关键字的方法,并小心地保持其安全性,但后来由于一些错误修复或新功能,该方法被修改并被参与该项目的新开发人员渲染为不安全。在这种情况下,使用同步块可以安全地访问属性。 - Tushar Joshi
只要没有synchronized方法包含任何阻塞调用,如Thread.sleep()或其中有需要锁定任何其他对象的synchronized代码块,我会同意这是正确的。 - Erick Robertson

0

两个线程都将执行,就像锁不存在一样。

访问method2的线程永远不会知道它应该获取一个锁,因此即使另一个线程持有锁,它也会执行。只有同步方法才会保留锁,因为同步是可选的,而且并不总是必要的。

如果两个线程都执行method1,则其中一个线程将被阻塞,直到另一个线程退出该方法并释放该锁。

为了确保只有一个线程执行并等待所有其他线程,您必须使两个方法都同步。


1
我只是对“好像锁不存在”有异议。没有线程会像锁不存在一样执行。 - Erick Robertson
1
@Erick Robertson,如果不检查锁定状态,那么它就好像不存在一样,在这两个示例中只有一个线程检查锁定状态,因此它将始终获取该锁定状态,而另一个线程则忽略它。没有任何线程会被阻塞,也没有任何状态得到保护。 - josefx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接