Java 为什么我们要声明此方法已同步?

Java 为什么我们要声明此方法已同步?,java,concurrency,Java,Concurrency,以这一类为例: //使用对象同步对共享可变数据的访问 //方法等待并通知所有。 公共类SynchronizedBuffer实现缓冲区 { private int buffer=-1;//由生产者线程和使用者线程共享 私有布尔值=false; //将值放入缓冲区 公共同步的void blockingPut(int值)抛出InterruptedException { //当没有空位置时,将线程置于等待状态 有空(有空) { //输出线程信息和缓冲区信息,然后等待 System.out.println

以这一类为例:

//使用对象同步对共享可变数据的访问
//方法等待并通知所有。
公共类SynchronizedBuffer实现缓冲区
{
private int buffer=-1;//由生产者线程和使用者线程共享
私有布尔值=false;
//将值放入缓冲区
公共同步的void blockingPut(int值)抛出InterruptedException
{
//当没有空位置时,将线程置于等待状态
有空(有空)
{
//输出线程信息和缓冲区信息,然后等待
System.out.println(“制作人尝试写入”);//仅用于演示
displayState(“缓冲区已满。生产者等待”);//仅用于演示
等待();
}
buffer=value;//设置新的缓冲区值
//指示生产者不能存储其他值
//直到使用者检索当前缓冲区值
占领=真;
displayState(“生产者写入”+缓冲区);//仅用于演示
notifyAll();//告诉等待的线程进入可运行状态
}//end方法blockingPut;释放SynchronizedBuffer上的锁
//从缓冲区返回值
public synchronized int blockingGet()引发InterruptedException
{
//当没有要读取的数据时,将线程置于等待状态
当(!占用)
{
//输出线程信息和缓冲区信息,然后等待
System.out.println(“消费者尝试读取。”);//仅用于演示
displayState(“缓冲区为空。使用者等待”);//仅用于演示
等待();
}
//指示生产者可以存储另一个值
//因为消费者刚刚检索到缓冲区值
占位=假;
displayState(“使用者读取”+缓冲区);//仅用于演示
notifyAll();//告诉等待的线程进入可运行状态
返回缓冲区;
}//end方法blockingGet;释放SynchronizedBuffer上的锁
//显示当前操作和缓冲区状态;仅用于演示
私有同步的void displayState(字符串操作)
{
System.out.printf(“%-40s%d\t\t%b%n%n”,操作,缓冲区,已占用);
} 
}//结束类SynchronizedBuffer
书中的这一段:

请注意,方法displayState是一个同步方法。这很重要,因为它也读取SynchronizedBuffer的共享可变数据。虽然一次只有一个线程可以获取给定对象的锁,但一个线程可以多次获取同一对象的锁,这称为可重入锁,使一个同步方法能够在同一对象上调用另一个方法。


为什么我们将方法
displayState()
声明为synchronized,尽管它仅从同步方法调用,因此当调用它时,调用线程已经在对象上拥有监视器锁?

您质疑此源代码是正确的。当一个方法拥有对象的监视器时,输入一个
synchronized
方法或块再次获取同一监视器,根本没有效果

作者也不太可能考虑到将来对代码的更改可能会在不拥有对象的监视器的情况下调用该方法。首先,该方法的全部目的是报告正在进行的操作,其次,强制执行正确的用法可能更简单

同样有趣的是,该方法的使用不一致。该方法始终报告
缓冲区的当前值
,但四个调用方中的两个在调用该方法之前将
缓冲区的当前值冗余地附加到操作参数字符串中。然后,在两个调用方之前还有其他显式print语句。由于它们的消息是“[Producer | Consumer]trysto[write | read]”,因此这些语句很可能在循环之前报告尝试,而不是在已知尝试失败的地方报告尝试

public class SynchronizedBuffer //implements Buffer
{
    private int buffer = -1; // shared by producer and consumer threads
    private boolean occupied = false;

    // place value into buffer
    public synchronized void blockingPut(int value) 
       throws InterruptedException
    {
        System.out.println("Producer tries to write."); // for demo only
        // while there are no empty locations, place thread in waiting state
        while(occupied) 
        {
           // output thread information and buffer information, then wait
           displayState("Buffer full. Producer waits.", buffer, occupied);// demo only
           wait();
        }

        buffer = value; // set new buffer value

        // indicate producer cannot store another value
        // until consumer retrieves current buffer value
        occupied = true;

        displayState("Producer writes ", buffer, occupied); // for demo only

        notifyAll(); // tell waiting thread(s) to enter runnable state
    } // end method blockingPut; releases lock on SynchronizedBuffer 

    // return value from buffer
    public synchronized int blockingGet() throws InterruptedException
    {
        System.out.println("Consumer tries to read."); // for demo only
        // while no data to read, place thread in waiting state
        while(!occupied)
        {
            // output thread information and buffer information, then wait
            displayState("Buffer empty. Consumer waits.", buffer, occupied);// demo only
            wait();
        }

        // indicate that producer can store another value 
        // because consumer just retrieved buffer value
        occupied = false;

        displayState("Consumer reads ", buffer, occupied); // for demo only

        notifyAll(); // tell waiting thread(s) to enter runnable state

        return buffer;
    } // end method blockingGet; releases lock on SynchronizedBuffer 

    // display current operation and buffer state; for demo only
    // not accessing the object, hence no synchronization needed
    private static void displayState(String operation, int buffer, boolean occupied)
    {
       System.out.printf("%-40s%d\t\t%b%n%n", operation, buffer, occupied);
    } 
}

通过将要报告的状态转换为参数,以线程安全的方式访问它们的责任显然在于调用者,并且通过使方法
静态
,方法不可能错误地访问对象状态。此外,变量值的冗余报告将在调用站点变得明显。

可能是为了证明您可以做到这一点(尽管这里确实没有必要)。如果该方法是公共的,或者被另一个未同步的方法调用,使它同步是必要的。@StephenC我不认为这是作者犯的错误,证据是他们指定了一个单独的段落来讨论这个方法,但我不理解,这就是为什么我问question@user9398992可能是防御性编程。尽管私有方法现在只从同步方法调用,但这并不意味着某些(其他)程序员在将来某个时候不会尝试从另一个非同步方法调用它。因为从已经同步的方法调用同步的方法是便宜的,它不会造成伤害,并且可以防止将来的意外误用。嗯,我认为这是一个错误。但我认为错误在于作者选择了一个糟糕的例子来说明这一点。@Andreas-可能。但既然这是一本书,他们应该解释一下。这显然不是真实世界的代码。。。