PrintNumber.java

本文介绍了一个简单的Java程序,该程序能够打印从100到200之间的所有整数,并且每行显示5个数字。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/******************************************************************************
 *  Compilation:  javac PrintNumber.java
 *  Execution:    java PrintNumber
 *  
 *  Print the integers from 100 to 200, 5 per line.
 *
 *  % java PrintNumber
 *
 ******************************************************************************/

public class PrintNumber { 

    public static void main(String[] args) {

        // print integers from 1000 to 2000, 5 per line
        int START = 100;
        int END   = 200;
        int PER_LINE = 5;
        for (int i = START; i <= END; i++) {
            System.out.print(i + " ");
            if ((i + 1) % PER_LINE == 0) System.out.println();
        }
        System.out.println();
    }
}

 

转载于:https://www.cnblogs.com/bayes/p/9662957.html

<think>我们分析用户的问题:用户在使用synchronized(PrintNumber.class)作为锁时,在同步块内调用了wait()方法,导致IllegalMonitorStateException异常。用户还询问了使用this作为锁对象的区别。 根据引用[1]和引用[2]的内容,wait()方法必须在持有对象监视器(即锁)的情况下调用,否则会抛出IllegalMonitorStateException。在用户提供的代码中,锁对象是PrintNumber.class(一个Class对象),而wait()方法调用的是当前对象(即PrintNumber的实例)的wait()方法。这就导致了锁对象和调用wait()方法的对象不一致。 具体原因: 1. 在同步块中,使用synchronized(PrintNumber.class)获取的是PrintNumber类的Class对象的锁。 2. 但是,在同步块内调用的wait()方法实际上等价于this.wait(),因为wait()是实例方法,它作用于当前对象(即PrintNumber的实例p)。 3. 由于锁对象是PrintNumber.class,而wait()方法要求当前线程必须持有目标对象(即this)的锁,因此当前线程并没有持有this对象的锁,所以抛出IllegalMonitorStateException。 解决方法: 要正确调用wait(),必须确保在同一个对象上同步并调用wait。有两种修改方式: 方式一:使用同一个对象作为锁 - 修改锁对象为this,即使用当前实例作为锁: ```java synchronized (this) { // ... this.wait(); // 现在锁对象和wait对象都是this,一致 } ``` 方式二:在Class对象上调用wait - 或者,在同步块内调用PrintNumber.class的wait()方法(但这样通常不推荐,因为Class对象是全局的,可能会影响其他线程): ```java synchronized (PrintNumber.class) { // ... PrintNumber.class.wait(); // 在同一个Class对象上调用wait } ``` 但是,在用户代码中,使用Class对象作为锁,然后调用实例的wait()方法,这是错误的。 另外,用户代码中还有一个问题:两个线程使用同一个Runnable实例(即同一个p),因此它们共享同一个number变量。但是,在wait()调用后,线程会等待,但没有任何线程去唤醒它们。因此,即使解决了异常,程序也会死锁,因为两个线程都进入了等待状态,没有人唤醒。 因此,我们还需要在适当的时候调用notify()或notifyAll()来唤醒等待的线程。通常,我们在一个线程操作完成后,唤醒其他线程,以便它们可以继续执行。 修改后的代码(使用this作为锁): - 在判断number<=100的代码块中,在number++后,我们调用notifyAll()唤醒另一个线程,然后自己调用wait()释放锁进入等待,这样另一个线程就有机会获取锁执行。 - 注意:当线程被唤醒时,它会从wait()之后继续执行,但此时需要重新检查条件(因为可能有多个线程,条件可能已经改变),所以通常使用循环来检查条件。 具体修改步骤: 1. 将锁对象改为this(或者使用同一个对象)。 2. 在wait()之后,另一个线程被唤醒时,应该继续判断条件,所以将if改为while(虽然这里只有两个线程,但为了规范,建议使用while)。 3. 在某个线程修改了共享变量后(number++),调用notifyAll()(或notify())唤醒其他线程。 修改后的代码示例: ```java class PrintNumber implements Runnable { private int number = 1; @Override public void run() { while (true) { synchronized (this) { // 修改为this,确保锁对象和wait对象一致 // 使用while循环防止虚假唤醒 while (number <= 100) { // 这里改为while,但也可以保留if,因为跳出循环的条件是break try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; // 唤醒其他线程,然后自己等待 try { this.notifyAll(); // 唤醒所有等待的线程 this.wait(); // 释放锁,进入等待状态 } catch (InterruptedException e) { e.printStackTrace(); } } // 当number>100时,跳出内层循环,但需要唤醒另一个线程,否则另一个线程会永远等待 this.notifyAll(); // 确保所有线程都能退出 break; // 跳出while(true)循环 } } } } ``` 注意:在循环结束后(即number>100),我们还需要调用notifyAll()来唤醒另一个还在等待的线程,否则程序无法结束。因为当number>100时,一个线程跳出循环并退出,但另一个线程可能在wait状态,需要被唤醒才能继续执行并退出。 另一种方式(使用Class对象作为锁)的修改: ```java class PrintNumber implements Runnable { private int number = 1; @Override public void run() { while (true) { synchronized (PrintNumber.class) { if (number <= 100) { // ... 同上 System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { PrintNumber.class.notifyAll(); // 唤醒所有在PrintNumber.class上等待的线程 PrintNumber.class.wait(); // 在PrintNumber.class上等待 } catch (InterruptedException e) { e.printStackTrace(); } } else { PrintNumber.class.notifyAll(); // 唤醒另一个线程,以便它也能退出 break; } } } } } ``` 但是,使用Class对象作为锁时,由于Class对象是全局唯一的,如果有多个不同的实例(这里我们只有一个实例p,但创建了两个线程,它们共享同一个p),所以使用Class对象作为锁可能会影响其他不相关的实例(如果存在多个PrintNumber实例的话)。但在这个例子中,因为两个线程共享同一个Runnable实例,所以使用this和Class对象都可以,但通常建议使用私有对象作为锁,避免使用Class对象或public的锁。 总结: 1. 出现IllegalMonitorStateException的原因是锁对象和调用wait()的对象不一致。 2. 解决方法是确保在同一个对象上同步和调用wait/notify。 3. 使用this作为锁对象时,调用this.wait()和this.notifyAll();使用Class对象时,调用Class对象上的wait/notify。 4. 注意在退出循环时唤醒其他线程,避免线程永远等待。 另外,关于synchronized锁对象和wait方法的使用区别: - synchronized可以锁定任意对象(实例对象或Class对象)。 - wait()方法必须由锁对象调用,即当前线程必须持有该对象的锁,并且调用的是该锁对象的wait()方法。 因此,在编写多线程程序时,务必确保同步块中使用的锁对象与调用wait/notify的对象是同一个对象。 最后,我们修改用户代码,并确保程序能够正常结束。 修改后的完整代码(使用this作为锁):</think>### Java中`synchronized(PrintNumber.class)`调用`wait()`导致`IllegalMonitorStateException`的原因解析 #### 1. **异常原因:锁对象与`wait()`调用对象不一致** 在您的代码中,`IllegalMonitorStateException`的根本原因是**锁对象与`wait()`调用对象不匹配**: ```java synchronized (PrintNumber.class) { // 锁定Class对象 // ... wait(); // 调用实例对象的wait() } ``` - `synchronized (PrintNumber.class)` 获取的是 `PrintNumber.class`(Class对象)的监视器锁 - `wait()` 实际等价于 `this.wait()`,调用的是当前`PrintNumber`实例对象的`wait()` - **关键问题**:线程持有`Class对象`的锁,却尝试调用`实例对象`的`wait()`,违反了Java的锁机制[^1] #### 2. `synchronized(PrintNumber.class)`与`this`的区别 | 特性 | `synchronized(PrintNumber.class)` | `synchronized(this)` | |---------------------|-----------------------------------------------|----------------------------------------| | **锁对象** | `PrintNumber`类的Class对象 | 当前实例对象 (`PrintNumber`实例) | | **作用范围** | 全局锁(影响该类的所有实例) | 实例级锁(仅影响当前对象) | | **内存位置** | 方法区(元空间) | 堆内存 | | **适用场景** | 静态资源共享/类级别同步 | 实例变量同步 | | **锁获取方式** | `Class`对象的内置锁 | 实例对象的内置锁 | | **`wait()`调用要求**| 必须在Class对象上调用:`PrintNumber.class.wait()` | 必须在实例上调用:`this.wait()` | #### 3. 解决方案:保持锁对象与`wait()`对象一致 **方案一:使用Class对象作为统一锁(推荐修改)** ```java synchronized (PrintNumber.class) { if (number <= 100) { // ... PrintNumber.class.wait(); // 在Class对象上调用wait } } ``` **方案二:使用实例对象作为统一锁** ```java synchronized (this) { // 锁定当前实例 if (number <= 100) { // ... this.wait(); // 在当前实例上调用wait } } ``` #### 4. 完整修复代码(使用实例锁) ```java class PrintNumber implements Runnable { private int number = 1; @Override public void run() { while (true) { synchronized (this) { // 统一使用实例锁 if (number > 100) break; try { Thread.sleep(100); System.out.println(Thread.currentThread().getName() + ":" + number); number++; this.notifyAll(); // 唤醒其他线程 this.wait(); // 释放锁并等待 } catch (InterruptedException e) { e.printStackTrace(); } } } } } ``` #### 5. 关键机制说明 1. **监视器锁(Monitor Lock)原理**: - 每个Java对象(包括Class对象)都有内置的监视器锁 - `synchronized`获取锁时,JVM会将对象头中的锁标志位改为"重量级锁"状态[^3] - `wait()`会释放当前持有的锁,并将线程加入对象的等待队列 2. **`wait()`的正确使用规范**: ```java synchronized (lockObject) { // 1. 获取锁 while (!condition) { // 2. 条件检查(防虚假唤醒) lockObject.wait(); // 3. 在锁对象上调用wait } // ... 执行业务逻辑 } ``` 3. **为什么需要`notifyAll()`**: - 在修改后的代码中,`this.notifyAll()`确保唤醒其他等待线程 - 否则所有线程都可能进入等待状态导致死锁 - 使用`notifyAll()`而非`notify()`可避免线程饥饿问题 #### 6. 最佳实践建议 1. **锁对象选择原则**: - 实例变量同步 → 使用`this`或专用锁对象 - 静态变量同步 → 使用`Class对象`或静态锁对象 ```java // 专用锁对象示例 private final Object lock = new Object(); synchronized (lock) { lock.wait(); } ``` 2. **避免Class对象锁的陷阱**: - Class对象是全局唯一的,可能导致意外的锁竞争 - 影响性能:可能阻塞不相关的操作 - 示例:`Class.forName()`也会获取Class锁 3. **替代方案考虑**: ```java // 使用ReentrantLock和Condition(更灵活) private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void run() { lock.lock(); try { while (!conditionMet) { condition.await(); // 类似wait() } // ... condition.signalAll(); // 类似notifyAll() } finally { lock.unlock(); } } ``` > **根本原则**:Java要求`wait()/notify()`的调用对象必须与`synchronized`使用的锁对象严格一致,这是JVM监视器锁机制的核心约束[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值