Object 类中方法
final void wait()
throws InterruptedException { wait(0) };
- 重要特性:当前线程等待(进入阻塞状态),主动释放锁
final native void wait(long timeout)
throws InterruptedException {…};
- 调用wait() 时,首先需要确保调用了wait() 方法的线程已经持有了对象的锁(monitor)。
- 调用wait() 方法的代码片段需要放在一个 synchronized块 或 synchronized方法中,这
样才可以确保线程在调用wait()方法 前已经获取到了对象的锁。 - 当调用wait() 后,当前线程陷入等待状态,该线程会主动释放掉 这个对象的锁,进入到等待
集合( wait set )。直到其他线程调用相同对象的notify()或notifyAll() 方法将其唤醒。 - 一旦这个线程被其他其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公
平竞争); - 只有当该线程获取到了这个对象的锁后,线程才会恢复到wait()方法调用前所处的状态,在继
续往下执行。 - 在单参数版本中,中断和虚假唤醒是可能的,并且此方法应始终在循环中使用:
synchronized (obj) {
while (< condition does not hold >)
obj.wait();
… // Perform action appropriate to condition
} - 如果在没有拿到这个对象的锁时,调用wait()方法,会导致非法参数异常:
java.lang.illegalargumentexception - 发生以下四种情况之一,才会唤醒线程:
- 1、另一个线程调用这个对象的notify方法,唤醒线程,而当前线程恰好被选中(随机)。
- 2、其他一些线程为此对象调用notifyAll方法。
- 3、其他线程中断当前线程。
- 4、指定的实时时间已过去,或多或少。但是,如果入参为0,则不考虑超时时长。
- 【 wait(long timeout) 】 等待到指定的时间(毫秒)。
final native void notify()
{…};
- 当调用对象的notify()方法时,它会随机唤醒该对象等待集合( wait set ) 中的任意一个线程,如果有多个线程都在等待着这个对象的锁,会随机选择唤醒其中一个线程。
- 被唤醒的线程是无法继续执行的,只有当前正在执行的线程 放弃或者让渡了 这个对象的锁,且被唤醒的线程在与其他线程公平竞争时抢到了对象的锁,被唤醒的线程才会继续往下执行。
- 此方法只能由已经持有了对象锁(monitor)的线程调用。线程通过以下三种方式之一可以持有对象的锁(monitor):
- 1、执行这个对象,被标识为 synchronized 的方法,获得对象的锁。
- 2、执行这个对象,一个 synchronized 语句块,获得括号里()对象的锁。
- 3、对于 class类型的对象,通过执行 这个类一个被标识为 synchronized 的静态方法,获得对象的锁。
- 在某一个时刻,只有唯一一个线程可以拥有对象的锁。
final native void notifyAll()
{…};
- 当调用对象的notifyAll()方法时,它会唤醒对象等待集合( wait set ) 中的所有处于等待(阻塞)状态的线程,这些线程被唤醒后,又会开始竞争对象的锁。
总结:wait()、notify()、notifyAll()
- 当调用wait() 时,首先需要确保调用了wait() 方法的线程已经持有了对象的锁(monitor)。(如果在没有拿到这个对象的锁时,调用wait()方法,会导致非法参数异常:
java.lang.illegalargumentexception) - 当调用wait() 后,该线程会主动释放掉这个对象的锁,进入到等待集合( wait set )。
- 当线程调用了wait() 后,进入到等待状态时,可以等待其他线程调用相同对象的notify()或notifyAll() 方法使得自己被唤醒。
- 一旦这个线程被其他其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到了这个对象的锁后,线程才会恢复到wait()方法调用前所处的状态,在继续往下执行。
- 调用wait() 方法的代码片段需要放在一个 synchronized块 或 synchronized方法中,这样才可以确保线程在调用wait()方法 前已经获取到了对象的锁。
- 对于一个单参数的版本来说,中断和一些虚假的唤醒,是有可能发生的。所以wait()方法应该始终处于循环当中。
- 当调用对象的notify()方法时,它会随机唤醒该对象等待集合( wait set ) 中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁。
- 当调用对象的notifyAll()方法时,它会唤醒对象等待集合( wait set ) 中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁。
- 在某一个时刻,只有唯一一个线程可以拥有对象的锁。
案例一 输出1010…
描述:
编写一个多线程程序,实现:
1.存在一个对象,该对象有一个int类型的成员变量counter,该成员变量初始值为0.
2.创建2个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减1.
3.输出该对象成员变量counter每次变化的值
4.最终输出的结果为:1010101010101010101010101010101010101010101...
找错误:
// 对象
class MyObject {
// Integer 默认值为 null
public volatile Integer num = 0;
public void increase() { num++; }
public void decrease() { num--; }
}
// 执行添加操作的线程
class MyIncreaseThread implements Runnable {
private MyObject myObject;
public MyIncreaseThread(MyObject myObject) { this.myObject = myObject; }
@Override
public void run() {
synchronized (myObject) {
for (int i = 0; i < 30; i++) {
while (myObject.num == 1) {
myObject.notify();
try {
myObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
myObject.increase();
System.out.print(myObject.num);
}
}
}
}
// 执行减去操作的线程
class MyDecreaseThread implements Runnable {
private MyObject myObject;
public MyDecreaseThread(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
synchronized (myObject) {
for (int i = 0; i < 30; i++) {
while (myObject.num == 0) {
myObject.notify();
try {
myObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
myObject.decrease();
System.out.print(myObject.num);
}
}
}
}
class Test2 {
public static void main(String[] args) {
MyObject myObject = new MyObject();
MyIncreaseThread increaseThread = new MyIncreaseThread(myObject);
MyDecreaseThread decreaseThread = new MyDecreaseThread(myObject);
Thread thread = new Thread(increaseThread, "添加线程");
thread.start();
Thread thread1 = new Thread(decreaseThread, "减去线程");
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// TERMINATED
System.out.println(thread.getState());
// WAITING
System.out.println(thread1.getState());
}
// 输出结果:【添加线程】正常关闭,【减去线程】陷入等待(无期限的死等)
// 10101010101010101010101010101010101010101010101010101010101
// TERMINATED
// WAITING
}
分析:为什么【减去线程】不能正常结束呢?
1、因为【增加线程】在最后的一次循环中,判断 if (myObject.num == 1) 结果为false。
2、然后将执行 num++,最后输出 num的值1。【添加线程】完成了所有任务,线程结束。
3、所以我们看到输出的最后一个数字是1,【添加线程】的状态是TERMINATED。
4、而【减去线程】还剩下最后一次循环,因为没人将其唤醒,所以会一直处于等待。
解决方法:
@Override
public void run() {
synchronized (myObject) {
for (int i = 0; i < 30; i++) {
if (myObject.num == 0) {
try {
myObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
myObject.decrease();
System.out.print(myObject.num);
// 所有的代码都不变,只要将MyDecreaseThread、MyIncreaseThread类中
// notify() 的方法调用,放在最后一行就行。
// 这样可以保证,线程执行完成后,一定会唤醒另一个线程。
myObject.notify();
}
}
}
// 输出结果:【添加线程】正常关闭,【减去线程】陷入等待(无期限的死等)
// 101010101010101010101010101010101010101010101010101010101010
// TERMINATED
// TERMINATED
案例二 生产者 / 消费者
描述:
写一个固定容量 同步容器,拥有put()、get()方法,getCount()方法,能够支持2个生产者线程以及
10个消费者线程的阻塞调用。
思路:
当生产者达到最大数量的时候,让生产者线程wait阻塞等待,否则继续生产,同时通知消费者进行消费。
当消费者消费到0个的时候,让消费者线程wait阻塞等待,否则继续消费,同时通知生产者进行生产。
实现方式一:wait() notifyAll()
class MyContainer<T> {
volatile LinkedList<T> list = new LinkedList<T>();
final private int MAX = 10; // 最多10个元素
public synchronized void put(T num) {
while (MAX == list.size()) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("添加线程:" + Thread.currentThread().getName());
list.add(num);
this.notifyAll();
}
public synchronized T get() {
while (0 == list.size()) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
T t = list.removeFirst();
System.out.println(Thread.currentThread().getName() + " get = " + t);
return t;
}
}
class Test5 {
public static void main(String[] args) {
MyContainer myContainer = new MyContainer();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int i1 = 0; i1 < 1; i1++) {
myContainer.get();
}
},"消费者线程-" + i).start();
}
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int i1 = 0; i1 < 5; i1++) {
myContainer.put(i1);
}
},"生产者线程-" + i).start();
}
// 这种实现方式有个问题:就是生产者线程调用notifyAll通知的时候,
// 有可能通知到生产者线程,消费者线程调用notifyAll通知的时候有可能通知到消费者线程,
// 所以还可以改进,下面使用Lock和Condition,解决这个问题。
}
//输出信息
//添加线程:生产者线程-0
//添加线程:生产者线程-0
//添加线程:生产者线程-0
//添加线程:生产者线程-0
//添加线程:生产者线程-0
//消费者线程-8 get = 0
//消费者线程-9 get = 1
//消费者线程-7 get = 2
//消费者线程-6 get = 3
//消费者线程-5 get = 4
//添加线程:生产者线程-1
//添加线程:生产者线程-1
//添加线程:生产者线程-1
//添加线程:生产者线程-1
//添加线程:生产者线程-1
//消费者线程-0 get = 0
//消费者线程-1 get = 1
//消费者线程-2 get = 2
//消费者线程-3 get = 3
//消费者线程-4 get = 4
}
实现方式二:Lock Condition
案例三 线程监控
描述:
实现一个容器,提供两个方法,add、size
写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
class MyContainer {
public volatile List<Integer> list = new ArrayList<>(10);
public boolean flag;
public void add(int num) {
System.out.println(num);
list.add(num);
}
public int getSize() {
return list.size();
}
public static void main(String[] args) {
MyContainer container = new MyContainer();
new Thread(() -> {
synchronized (container) {
while (container.getSize() != 5) {
try {
container.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("集合数量到5了");
container.flag = true;
container.notify();
}
},"监控线程").start();
new Thread(() -> {
synchronized (container) {
for (int i = 0; i < 10; i++) {
if (5 == i) {
try {
container.notify();
container.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (container.flag) {
System.out.println("线程执行结束");
break;
}
container.add(i);
}
}
}, "添加线程").start();
}
}
// 输出信息
// 0
// 1
// 2
// 3
// 4
// 集合数量到5了
// 线程执行结束
注:资料主要来源【张龙】