线程最大的问题,就是在CPU当中是抢占式执行,随机调度的。但是,为了控制线程执行的先后顺序,可以通过一些api来让线程主动放弃cpu的使用权。从而达到让有些线程先执行,有一些后执行的效果。
目录
含义:
一、wait()方法的使用
wait()方法是Object类的成员方法,它的含义是,如果在某一个线程的任务当中调用了object.wait()方法,会让当前的线程进入阻塞状态.
如果wait()方法的参数列表为空,那么就会"死等",直到有其他的线程来唤醒当前线程。在等待的过程中,"锁"会释放掉。允许其他线程继续获取锁(object)。
但是在Object类当中,wait()方法是存在多态的。如果不添加任何参数,那么默认是死等。直到有其他线程把它唤醒。如果添加了参数,那么含义就是:wait()到一定的时间。等到了时间,就会继续尝试获取锁。
但是线程一运行,就会出现以下报错:
IllegalMonitorStateException
把上面这个单词拆分一下,就变成: Illegal MonitorState Exception
即:非法的锁监视器异常;那么这个异常,是哪里存在状态异常呢?
锁的状态,无非就是2种,一种是被加锁的状态,另外一种就是被解锁的状态。
此时,锁状态异常就是,当程序期待锁的状态是什么,但是锁的状态并不是这个状态,这也就触发了锁状态异常。
wait()方法的执行逻辑:
wait()是Object类的方法,调用这个方法需要对象。如果调用wait()方法的对象已经被线程加锁了,再调用wait()方法,就会让加锁的线程释放锁,其他线程可以获取到当前对象的对象锁,然后自己进入阻塞状态。但是,如果调用的对象没有被线程加锁,直接调用wait(),就会抛出锁状态异常:
代码实现:
public class ThreadDemo16 {
public static void main(String[] args) {
Object object=new Object();
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
synchronized (object){
System.out.println("阻塞之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("阻塞之后");
}
}
});
thread.start();
}
}
可以看到,针对object对象加锁之后,只能看到阻塞之前的情况,无法看到阻塞之后这一句话。
但是进程一直没有结束。
可以看到,运行结果一直是:阻塞之前......
wait()方法和sleep()方法的区别?
①语法上面的不同,wait()方法属于Object类的成员方法,sleep()方法属于Thread类的静态方法;
②二者都会让线程进入阻塞状态,但是区别在于:
当一个线程调用了Thread.sleep(t)方法之后,会让自身进入阻塞状态,达到了时间t(单位为毫秒)之后,线程会继续回到就绪队列当中,等待操作系统的调度。如果Thread.sleep()出现在一个被加锁的代码块或者方法当中,不会释放锁。
但是,如果在synchronized代码块当中,如果被加锁的对象调用了wait()方法之后,会释放锁,让其他线程可以继续获取当前对象的锁,然后当前线程进入阻塞状态。
如果wait()方法没有指定时间,就会默认死等下去,直到其他线程唤醒当前线程。如果指定了wait()的时间,那么到了指定的时间之后,当前线程会继续尝试针对对象加锁,执行接下来的任务。
二、notify()方法
含义:
notify()方法是object类的成员方法。当调用这个方法之前,需要线程针对某一个对象object来加锁,在加锁之后,在接下来的同步代码块(synchronized修饰)当中才可以调用notify()方法,此方法用来唤醒进入阻塞状态的线程。
但是"唤醒"之后,调用notify()方法的线程不会立即释放锁,仍然会继续执行任务,直到执行完同步代码块的任务之后,才最把锁释放。
唤醒之后,原来调用object.wait()的线程会从阻塞队列当中离开,尝试获取锁;但是,因为此时无法再次获取锁,所以,又会进入阻塞队列。
代码实现:
运行结果:
注意事项:
①两个线程操作的wait(),notify()操作,一定要操作的是同一个对象,才会让wait()方法和notify()方法都生效。
②由于操作系统针对线程的调度是随机的,因此,也有可能让后面start()的t2线程先执行,那么,如果t2先notify()的话,t2就相当于空打一炮,t1依然会在执行完wait()方法之后一直阻塞。
三、notifyAll()方法
当多个线程尝试获取同一个对象(object)的锁的时候,只会有一个线程获取到锁,其他线程进入阻塞等待状态。那么此时,如果获取锁的线程调用了object.notifyAll()方法之后,会唤醒其他都正在等待的线程。
与notify()不同的是,notify()只会随机唤醒一个正在阻塞等待的线程。而notifyAll()是全部唤醒。
来一段代码,观察一下notify()方法:
给出一个业务场景,同时启动7个线程,每个线程的任务当中,都会针对同一个对象(object)加锁,这会造成其中一个线程获取到锁,其他线程阻塞等待的情况。
所有线程都启动之后,主线程调用object.notify()方法,观察一下运行的情况:
public static void main(String[] args) {
Object lock=new Object();
for (int i=1;i<=7;i++)
{
int I = i;
//让每个线程都针对lock对象加锁,其中一部分线程去阻塞等待
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程"+ I+"正在等待");
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程"+I+"已经被唤醒");
}
});
t1.start();
}
//让其他线程都去先执行任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//主线程尝试唤醒其他线程
synchronized (lock){
lock.notify();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果截图:
可以看到,此时notify()方法仅仅是随机唤醒了一个正在等待的线程,但是进程并没有结束。其他没有被唤醒的线程继续阻塞等待
再观察一下notifyAll()方法:
public static void main(String[] args) {
Object lock=new Object();
for (int i=1;i<=7;i++)
{
int I = i;
//让每个线程都针对lock对象加锁,其中一部分线程去阻塞等待
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程"+ I+"正在等待");
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程"+I+"已经被唤醒");
}
});
t1.start();
}
//让其他线程都去先执行任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//主线程尝试唤醒其他线程
synchronized (lock){
lock.notifyAll();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果截图:
可以看到,此时唤醒了所有正在等待的线程,进程顺利结束。
四、A,B,C三个线程,如何按照顺序输出A,B,C?
思路:这一题可以使用join来完成,也可以使用notify(),wait()这两个方法配合完成。
如果使用wait(),notify()来配合完成,首先需要两个对象,一个是object1,另外一个是object2.用来完成线程之间的相互通知。
就需要考虑使用,先让B,C线程并发执行,因为C在B之后输出,因此在线程B输出完"B",之后,会通知线程C输出B。
接着,让线程A最后启动。线程A启动之后,通知线程B执行。
图解:
代码实现:
public class ThreadHomeWork3 {
public static void main(String[] args) {
Object lock1=new Object();
Object lock2=new Object();
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
//thread1线程优先唤醒lock1
synchronized (lock1){
lock1.notifyAll();
}
}
},"A");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1){
try {
lock1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
synchronized (lock2){
lock2.notifyAll();
}
}
},"B");
Thread t3=new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock2){
try {
lock2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
}
},"C");
t2.start();
t3.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.start();
}
}