3

据我了解,下面的代码在synchronized块中this是一个计数器的实例。

问题一:在下面的例子中,这是否意味着当线程 A 到达synchronized块时,线程 B 被阻止对 Counter 的实例做任何事情?换句话说,这是否意味着线程可以继续按他们看到的方式执行,但是在任何一个到达 synchronized块时,另一个停止对类做任何事情,直到块退出?

public class Counter {

    public void increment() {
        // Some code

        synchronized (this) {  // <---- "this" is an instance of Counter
             // Some more code
        }
    }
}

将上面的代码与

public class Counter {

    List<String> listOfStrings = new ArrayList<String>();

    public void increment() {
        // Some code

        synchronized (listOfStrings) {  
             // Some code that deals with 
             //    listOfStrings 
        }
    }
}

问题2:在上面的例子中,一旦线程A到达synchronized块,线程B可以继续读写类中的任何东西,除了listOfStringsArrayList,它是块mutex中的a。synchronized它是否正确?

问题3:假设如果我们需要对多个对象进行修改是否更正确,this我们mutex应该使用?

例如:

public class Counter {

    List<String> listOfStrings = new ArrayList<String>();
    List<Integers> listOfIntegers = new ArrayList<Integers>();

    public void increment() {
        // Some code

        synchronized (this) {  
             // Some code that deals with 
             //    listOfStrings and listOfIntegers
        }
    }
}

我理解正确吗?如果我遗漏了什么,请更正。

4

5 回答 5

5

线程 B 被阻止对 Counter 的实例执行任何操作?

不,线程 B 被阻止进入同步的代码块,它仍然可以进入其他方法:那些不同步的方法和那些使用不同对象同步的方法。线程 B 不能只访问使用已被不同线程占用的对象同步的块(同步锁是可重入的)。

线程 B 可以继续读写类中的任何内容,除了listOfStrings

listOfStrings并非如此,在一个块中用作互斥锁这一事实synchronized并不意味着其他线程无法显式访问该对象。这仅意味着其他线程无法访问由同一对象保护的同步块。因此,如果您想保护对listOfStrings对象的访问,则访问该对象的所有方法都必须同步并使用相同的锁(例如listOfStrings)。

顺便说一句,您同步的每个对象都应该final避免头痛。

假设如果我们需要对多个对象进行修改,这就是我们应该使用的互斥体,是否更正确?

是和不是。考虑以下情况:

List<String> listOfStrings = new ArrayList<String>();
List<Integers> listOfIntegers = new ArrayList<Integers>();
Set<String> setOfStrings = new HashSet<String>();
Set<Integers> setOfIntegers = new HashSet<Integers>();

如果一种方法只访问列表而第二种方法只访问集合,您可以安全地使用两个锁——一个用于第一种方法,第二个用于第二种方法。同步this不会受到伤害,但会影响性能:

private final Object listLock = new Object();
private final Object setLock = new Object();

然后:

synchronized (listLock) {  
     // Some code that deals with 
     // setOfStrings and setOfIntegers
}

//...

synchronized (setLock) {  
     // Some code that deals with 
     // setOfStrings and setOfIntegers
}
于 2012-07-07T16:20:26.497 回答
4

快速回答:

  1. 来自的锁synchronized是可重入的,这意味着获得它的线程仍然可以进入同一对象上的任何其他同步块。任何其他想要进入该对象上的任何同步块的线程都将被阻止。

  2. 对象上的同步并不意味着该对象不能被修改。备注:不要将同步对象视为互斥锁。任何线程都可以进入不同步对象的类的方法。如果该方法修改了对象,则没有什么可以阻止它。要获得您想要的,您需要使同步对象的类本身是线程安全的。

  3. 你不正确:你是对的,但你做得过火了。您不应该跳到使用同步所需类的最大范围。事实上,你不应该依赖this一般。最好将内部对象(甚至是“虚拟对象”,例如 a new Object())锁定到该类,否则将允许使用该类中的对象的任何代码尝试在它们上进行同步。

于 2012-07-07T16:22:24.147 回答
2

同步块中的对象只是一个令牌,这意味着持有令牌的线程可以进入执行块(同步内部的内容),而不是锁定对该对象的访问。

于 2012-07-07T16:18:21.913 回答
1

在 java 中,任何对象都可以用作互斥锁 - java.lang.Object 具有充当互斥锁的能力,并具有 wait() 和 notify() 方法。

同步块引用对象 - 一旦线程进入同步块,作为互斥体传递的对象被锁定。每当线程尝试进入同步块时,它都会检查互斥对象上的锁。如果对象被锁定,线程会等待对象。一旦工作线程退出同步块 - 调用对象的通知方法并通知等待线程 - 在所有等待线程中,只有一个线程会锁定对象并进入同步块,其余线程继续等待对象引用。

决定将哪个对象用作互斥锁非常重要 - 如您的场景中所述。

另一个需要注意的重点是同步块不会阻止多个线程编辑互斥对象。这是参考您的问题 2 - 特别是以下句子:

在上面的例子中,一旦线程 A 到达同步块,线程 B 可以继续读取和写入类中的任何内容,但 listOfStrings ArrayList 除外,它是同步块中的互斥锁

假设线程 B 不能读/写 listOfStrings,因为它被用作互斥体,这是错误的。以下场景可能允许 listOfString 被多个线程处理:

public class Counter {      

List<String> listOfStrings = new ArrayList<String>();  

public void decrement(){
   listOfStrings = new ArrayList<String>();
}

public void increment() {      
    // Some code      

    synchronized (listOfStrings) {        
         // Some code that deals with       
         //    listOfStrings       
    }      
 }      
}      

在上述场景中,线程 A 可能调用增量 - 进入同步块锁定 listOfStrings,同时线程 B 可能调用减量,即使它使用增量互斥体,它也能够更新 listOfStrings。之所以如此,是因为同步块不会阻止互斥对象的更新——它只是确保 2 个线程不会进入具有相同互斥对象的同步块。互斥锁这个名字突出了这个本质——互斥——使用同一个互斥锁不是强制,而是不同代码块的相互理解。

于 2012-07-07T16:46:17.623 回答
0

关于问题1:多个线程可以同时执行同步块外的代码,但一次一个线程可以执行同步块内的代码。通常,您希望将任何修改Counter类的实例变量的代码放在块内,而仅适用于当前方法的局部变量的代码可以放在同步块之外。

关于问题二和三:您可以在您想要的任何对象上进行同步,只要所有访问实例变量的代码Counter都在同步块内(假设您不希望在写入时从计数器读取数据)操作正在进行中)。我实际上建议在其中一个列表上同步,或者Object在这种情况下甚至是显式的私有独立实例 - 如果不需要此类之外的代码直接获取其同步监视器,通常最好通过同步来禁用它类内部的私有对象。

于 2012-07-07T16:25:42.377 回答