将参数传递给同步块的目的是什么?



我知道

同步代码块时,可以指定哪个对象的锁 您想用作锁,因此您可以使用一些 第三方对象作为这段代码的锁。这给你 能够有多个锁用于代码同步 单个对象。

但是,我不明白将参数传递给块的必要性。因为我是否传递 String 的实例并不重要,所以无论传递给块的参数如何,同步块都可以完美地工作。

所以我的问题是,无论如何,同步块是否会阻止两个线程同时进入关键部分。那么为什么需要传递论点。(我的意思是默认情况下获取某个随机对象的锁)。

我希望我正确地提出了我的问题。

我已经尝试了以下示例,其中随机参数是同步块。

public class Launcher {
public static void main(String[] args) {
AccountOperations accOps=new AccountOperations();
Thread lucy=new Thread(accOps,"Lucy");
Thread sam=new Thread(accOps,"Sam");
lucy.start();
sam.start();
}
}

使用非静态同步块:

public class AccountOperations implements Runnable{
private  Account account = new Account();

public void run(){
for(int i=0;i<5;i++){
makeWithdrawal(10);                 
}
}
public  void makeWithdrawal(int amount){
String str="asd"
synchronized (str /* pass any non-null object the synchronized block works*/) {
if(account.getAmount()>10){
try{
Thread.sleep(5000);             
}catch(InterruptedException e){
e.printStackTrace();
}
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
}else{
System.out.println("Insufficient funds "+account.getAmount());
}
}
}
}

使用静态同步块:

public class AccountOperations implements Runnable{
private static Account account = new Account();

public void run(){
for(int i=0;i<5;i++){
makeWithdrawal(10);                 
}
}
public static void makeWithdrawal(int amount){
synchronized (String.class /* pass any class literal synchronized block works*/) {
if(account.getAmount()>10){
try{
Thread.sleep(5000);             
}catch(InterruptedException e){
e.printStackTrace();
}
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
}else{
System.out.println("Insufficient funds "+account.getAmount());
}
}
}
}

因为我是否传递 String 的实例并不重要,所以无论传递给块的参数如何,一些随机类的实例都可以完美地工作,因为同步块可以完美地工作。

该参数的用途有两个:

  1. 它可以同步同一对象上的其他块,这样,如果有两个代码块可能会更改同一对象的状态,它们就不会相互干扰。

    例如:

    public void getSum() {
    int sum = 0;
    synchronized (this.list) {
    for (Thingy t : this.list) {
    sum += t.getValue();
    }
    }
    return sum;
    }
    public void addValue(int value) {
    synchronized (this.list) {
    this.list.add(new Thingy(value));
    }
    }
    

    在那里,重要的是我们将两个访问同步到跨线程list。我们不能让某些东西调用addValue并在另一个线程调用getSum时踩在列表上。

  2. 这样可以确保与正确的粒度同步。如果要序列化对特定于实例的资源的访问,则跨实例执行此操作没有意义;您应该允许多个线程进入块,前提是它们在不同的实例上运行。这就是为什么你会在特定于实例的资源的this(或更常见的某个this字段)上同步,或者如果它是静态资源,则同步类(或更常见的某个类字段)。同样,如果您只需要保护this的特定字段,则无需同步。

    例如:

    // (In MyClass)
    public void getThingySum() {
    int sum = 0;
    synchronized (this.thingyList) {
    for (Thingy t : this.thingyList) {
    sum += t.getValue();
    }
    }
    return sum;
    }
    public void addThingy(Thingy t) {
    synchronized (this.thingyList) {
    this.thingyList.add(t);
    }
    }
    public void getNiftySum() {
    int sum = 0;
    synchronized (this.niftyList) {
    for (Nifty n : this.niftyList) {
    sum += n.getValue();
    }
    }
    return sum;
    }
    public void addNifty(Nifty n) {
    synchronized (this.niftyList) {
    this.niftyList.add(t);
    }
    }
    

    在那里,我们同步对this.thingyList的访问this.thingyList,而不是thisMyClass.class.如果一个线程调用getThingySum而另一个线程调用addNifty,这很好,因此同步this将是矫枉过正的。


再举str的例子:

public  void makeWithdrawal(int amount){
String str="asd"
synchronized (str /* pass any non-null object the synchronized block works*/) {
if(account.getAmount()>10){
try{
Thread.sleep(5000);             
}catch(InterruptedException e){
e.printStackTrace();
}
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
}else{
System.out.println("Insufficient funds "+account.getAmount());
}
}
}

那里的注释不正确,任何非null实例都无法充分保护该代码。上述方法似乎有效的原因是字符串实习:所有线程都使用相同的String实例,因为字符串文本会自动放入字符串intern池中。(这意味着你过度同步;它是JVM范围的,而不是特定于实例的。所以它有效,但不是因为它只是任何对象。如果将其从以下位置更改为:

String str = "asd";

Object o = new Object();

并在此同步,它不会序列化对帐户的访问。

在您的示例中,要同步的正确内容是this.account

如果无论如何同步块会阻止两个线程同时进入关键部分。那为什么需要通过论点呢?

同步块根据传递给它的对象决定停止哪些线程。传递的对象用作由同步块保护的监视器部分的标识符。

程序中可能有许多监视器部分,所有这些部分都可以彼此并发执行。例如,如果必须同时访问两个不相关的集合,则可以为每个集合设置单独的监视部分。这样,仅当其他线程已经在访问同一集合时,线程才会停止;允许访问两个不同集合的两个不同线程同时进行。

你的第一个例子是不平凡的。它工作的原因是字符串对象被初始化为字符串文本。由于 word 的实习,所有进入函数的线程都将获得相同的String对象,因此同步块将正确保护监视器部分。

此外,您可能需要将实例 X 的对象传递给同步的参数,以防您需要将其传递给等待队列(通过使用 X.wait())。然后,从另一个线程中,您可以通过在 X 上调用 notify() 来通知对象(在需要时)。

最新更新