Thread和Object类中重要方法
Wait和notify的基本用法
- 代码的执行顺序
- 证明wait释放锁
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程" + Thread.currentThread().getName() + "获得到了锁");
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
// 为了证明notify执行,让线程暂时sleep
Thread.sleep(200);
thread2.start();
}
}
输出
Thread-0开始执行
线程Thread-1调用了notify()
线程Thread-0获得到了锁
从输出的情况来看是符合预期的
首先让Thread1进入到wait状态,然后这时候是Thread1的代码块就不会继续往下执行了,那么到Thread2中执行notify进行唤醒,唤醒之后Thead2会继续执行他自己的代码块,因为这也是遵循synchronized只会同时执行一个代码块,之后,Thread1就继续往下执行。
这个其实就看出,wait是释放了锁,如果不释放,Thread2是无法执行的,也就无法唤醒。
notify和notifyAll的区别
使用notifyAll
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
System.out.println("ThreadC notified");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + "got resourceA lock");
try {
System.out.println(Thread.currentThread().getName() + "wait to start");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + "waiting to end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出
Thread-0got resourceA lock
Thread-0wait to start
Thread-1got resourceA lock
Thread-1wait to start
ThreadC notified
Thread-1waiting to end
Thread-0waiting to end
使用notify
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notify();
System.out.println("ThreadC notified");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + "got resourceA lock");
try {
System.out.println(Thread.currentThread().getName() + "wait to start");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + "waiting to end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出
Thread-1got resourceA lock
Thread-1wait to start
Thread-0got resourceA lock
Thread-0wait to start
ThreadC notified
Thread-1waiting to end
从上面的两个例子就可以看出,notifyAll就会将所有的线程进行唤醒,而notify就只是唤醒了其中的一个,另一个还是在等待中。
wait只释放当前的那把锁
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock");
}
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock");
}
try {
System.out.println("ThreadA release resourceA lock");
resourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println("ThreadB got resourceA lock");
}
synchronized (resourceB) {
System.out.println("ThreadB got resourceB lock");
}
}
});
threadA.start();
threadB.start();
}
}
输出
ThreadA got resourceA lock
ThreadA got resourceB lock
ThreadA release resourceA lock
ThreadB got resourceA lock
从上面的例子就可以看出,在线程A中释放了resourceA,所以在线程B中是可以拿到resourceA的,但是线程A中并没有释放resourceB的那把锁,所以线程B是拿不到resourceB的。也就验证了wait只释放当前的那把锁。
wait、notify、notifyAll特点、性质
- 必须先拥有monitor(synchronized锁),否则是会抛异常的;
- notify只能唤醒其中一个,唤醒哪一个并不是我们觉得的,因为我们并没有传参,是取决于JVM的实现;
- 三个都是属于Object类;
- 这些功能相当于是底层的用法,功能类似Condition(这是JDK封装好的),他的功能和这些非常的类似;
- 同时持有多个锁的情况
在这里还要两种特殊情况:
1.从Object.wait()状态刚被唤醒时,同城不能立刻抢到monitor锁,就会从Waiting先进入到Blocked状态,抢到锁之后在转换到Runnable状态(官方文档也有说明)
官网说明文档地址
2.如果发生异常,可以直接跳到终止Terminated状态,不必遵循路径,如Waitng直接跳到Terminated
用wait/notify来实现 生产者消费者模式
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
maxSize = 10;
storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("仓库里有了" + storage.size() + "个产品");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + ",仓库还剩下" + storage.size());
notify();
}
}
这里的输出就不贴了,可以自己直接跑一下这段代码来看输出。
从输出上来看,一开始的时候是10个生产,10个消费,到后面就不再以10这样出现了可能是生产6个,消费6个,因为在生产者和消费者中各执行一次都会进行notify,并且代码块在synchronized 中执行,只能一个一个执行。这里就通过了wait和notify实现了生产者和消费者。
两个线程交替打印0-100奇偶数
synchronized实现
效果可以实现,但是效率太低,因为两个线程会竞争锁,有可能是偶数的线程一直拿到锁,但是并不能打印数值
public class WaitNotifyPrintOddEvenSyn {
// 新建两个线程
// 一个只处理偶数,第二个处理技术(用位运算)
// 用synchronized实现
private static int count;
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 0) {
System.out.println(Thread.currentThread().getName() + ":" + count);
count++;
}
}
}
}
}, "偶数").start();;
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count);
count++;
}
}
}
}
}, "奇数").start();
}
}
wait和notify
public class WaitNotifyPrintOddEveWait {
// 拿到锁,就打印
// 打印完,唤醒其他线程,就休眠
private static int count;
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(new TurningRunner(), "偶素").start();
new Thread(new TurningRunner(), "奇数").start();
}
static class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= 100) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ":" + count);
count++;
lock.notify();
if (count < 100) {
try {
// 如果任务还没有结束,就让出当前的锁,自己休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
为什么wait()需要在同步代码块内使用,而sleep()不要
这个最主要是为了保证通信可靠,防止死锁或者永久等待的发生,如果不把wait或者notify放在同步代码块中,那么可能在执行线程之前就切到了另外的线程上,这时候另外的线程执行完毕了之后才来执行wait,但是实际上我们是想要执行了wait之后再去执行其他的线程,所以如果没有同步代码块保护,就可以切过去,这样就可能会造成死锁或者永久等待的发生。
wait,notify这些是需要相互配合的,所以都在同步代码块中进行,而sleep是只针对自己单独线程的,和其他线程关系并不大。
为什么线程通信的方法wait,notify,notifyAll被定义在Object类里,而sleep定义在Thread里
这个是因为wait,notify,notifyAll其实是一个锁级别的操作,锁是属于某一个对象的,假如这些方法在Thread中,一个线程确实是可以持有多个锁,但是这些锁直接是需要进行配合的,所以,这些方法在Thread中的话就不够灵活。
wait方法是属于Object对象的,那调用Thread.wait会怎么样呢
Thread也是一个对象,集成自Object,如果当做一个普通的锁是没有问题,但是当一个线程退出的时候,会自动去实行notify,这样会干扰我们设计的流程。所以调用wait或者创建锁对象的时候,不要调用Thread类。