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

Joh*_*mbo 18 java multithreading synchronization synchronized-block

我知道

同步代码块时,指定要将哪个对象的锁用作锁,因此您可以使用某些第三方对象作为此代码段的锁.这使您能够在单个对象中拥有多个用于代码同步的锁.

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

    }

}
Run Code Online (Sandbox Code Playgroud)

使用非静态同步块:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

使用静态同步块:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 26

因为我是否传递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));
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在那里,重要的是我们将两个访问同步到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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在那里,我们同步访问this.thingyListon this.thingyList,not 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());
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

那里的评论不正确,任何非null实例都不能充分保护该代码.上面似乎工作的原因是字符串实习:String所有线程都使用相同的实例,因为字符串文字会自动放入字符串intern池中.(这意味着你过度同步;它是JVM范围的,而不是特定于实例的.)所以它可以工作,但不是因为它只是任何对象.如果您更改了:

String str = "asd";
Run Code Online (Sandbox Code Playgroud)

Object o = new Object();
Run Code Online (Sandbox Code Playgroud)

并且在此上同步,它将无法序列化对帐户的访问.

在您的示例中,正确的事情是同步this.account.

  • 谢谢 TJ 克劳德。+1 (2认同)
  • woooow!这是我在网上读到的有关线程的最佳解释.老板帽子! (2认同)

das*_*ght 7

如果无论如何同步块阻止两个线程同时进入临界区.那么为什么需要传递一个论点呢?

同步块根据传递给它的对象决定要停止的线程.您传递的对象充当由synchronized块保护的临界区的标识符.

您的程序中可能有许多关键部分,所有这些部分可以彼此同时执行.例如,如果必须同时访问两个不相关的集合,则可以为每个集合设置单独的临界区.这样,只有当其他线程已经访问同一个集合时,才会停止线程; 访问两个不同集合的两个不同线程将被允许同时进行.

你的第一个例子是非平凡的.它起作用的原因是字符串对象被初始化为字符串文字.由于文字的实习,进入该函数的所有线程将获得相同的String对象,因此同步块将正确保护临界区.