关于Java synchronized块的3个快速问题

时间:2012-07-07 16:14:33

标签: java synchronized

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

问题1:在下面的示例中,这是否意味着当线程A到达synchronized块时,线程B被阻止对计数器的实例做任何事情?换句话说,这是否意味着Threads可以继续按照他们的意愿执行,但是当它们到达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可以继续读取和写入类中的任何内容,listOfStrings除外ArrayList,mutex块中的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
        }
    }
}

我理解正确吗?如果我错误地说了什么,请更正。

5 个答案:

答案 0 :(得分:5)

  

是否阻止线程B对计数器的实例做任何事情?

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

  

线程B可以继续读取和编写类中的任何内容,listOfStrings

除外

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

BTW您同步的每个对象应该是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
}

答案 1 :(得分:4)

快速回答:

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

  2. 对象的同步并不意味着无法修改对象。 备注:不要将同步对象视为除互斥锁之外的任何其他对象。任何线程都可以输入不在对象上同步的类的方法。如果该方法修改了对象,则没有什么可以阻止它。要获得所需的内容,需要使同步对象的类本身具有线程安全性。

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

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

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

需要注意的另一个要点是synchronized块不会阻止多个线程编辑互斥对象。这是参考你的问题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可能会调用increment - 进入同步块锁定listOfStrings,同时线程B可能会调用递减,即使它在增量中使用了互斥锁,它也能够更新listOfStrings。这是因为synchronized块不会阻止互斥对象的更新 - 它只是确保2个线程不会进入具有相同互斥对象的同步块。 mutex这个名字突出了这个本质 - 相互排斥 - 它不是完全的,而是通过不同的代码块相互理解来使用相同的互斥体。

答案 4 :(得分:0)

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

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