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

我知道那件事

同步代码块时,可以指定要用作锁的对象锁,以便例如,可以使用某些第三方对象作为此段代码的锁。这使您能够在单个对象中具有多个用于代码同步的锁。

但是,我不明白是否需要将参数传递给块。因为我是否将 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());
            }
        }

    }

}

答案 1

因为我是否将 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));
        }
    }
    

    在那里,重要的是我们要跨线程同步两个访问。我们不能在另一个线程调用 时在列表中调用和踩踏某些内容。listaddValuegetSum

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

    例如:

    // (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);
        }
    }
    

    在那里,我们将访问同步到 on ,而不是 或 。如果一个线程正在调用而另一个线程调用 ,这很好,所以在 上同步会大材小用。this.thingyListthis.thingyListthisMyClass.classgetThingySumaddNiftythis


重温您的示例: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());
        }
    }

}

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

String str = "asd";

Object o = new Object();

并在此过程中进行同步,它将对序列化对帐户的访问没有任何作用。

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


答案 2

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

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

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

您的第一个示例并非易事。它的工作原理是字符串对象初始化为字符串文本。由于文字的滞留,所有进入函数的线程将获得相同的对象,因此同步块将正确保护监视部分。String