Object和Thread一些和线程阻塞相关的常用方法
Object中的方法
每一个对象的对象头中,会有一个锁标志位。可以通过获取和释放对象锁的方式,来达到阻塞线程的目的。通过synchronized关键字可以获取到对象的锁。
wait()
wait()方法会释放对象锁。如果要使用wait()方法,就必须先获取到对象的锁。所以wait()方法必须在synchronize修饰的方法或同步代码块中使用。
// Object.wait()方法使用
public class ObjectWait implements Runnable{
public static void main(String[] args) {
Thread thread = new Thread(new ObjectWait());
thread.start();
}
@Override
public void run() {
synchronized (this) {
try {
System.out.println("已经获取到对象的锁");
System.out.println("开始调用对象的wait方法");
this.wait();
System.out.println("线程执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果
已经获取到对象的锁
开始调用对象的wait方法
"线程执行结束"这句没有打印,说明线程没有执行完成。因为wait()不加时间参数表示无限等待。要如何唤醒无限等待的线程呢?这就需要用到下面两个方法:notify()和notifyAll()
notify()和notifyAll()
notify()和notifyAll()都可以唤醒调用wait()方法等待的线程,它们区别是:notify()只会随机唤醒一个等待的线程,notifyAll()会唤醒所有等待的线程。下面我们用代码演示下这两者的区别。
notify()方法:
package theadcoreknowledge.objectandthreadcommonmethod;
// 使用notify随机唤醒一个等待的线程
public class NotifyOneWaitThread implements Runnable {
public static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
NotifyOneWaitThread notifyOneWaitThread = new NotifyOneWaitThread();
Thread thread = new Thread(notifyOneWaitThread);
Thread thread2 = new Thread(notifyOneWaitThread);
thread.start();
thread2.start();
Thread.sleep(100);
synchronized (lock) {
// 随机唤醒一个等待的线程
lock.notify();
}
}
@Override
public void run() {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + "已经获取到对象的锁");
System.out.println(Thread.currentThread().getName() + "开始调用对象的wait方法");
lock.wait();
System.out.println(Thread.currentThread().getName() + "执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
Thread-0已经获取到对象的锁
Thread-0开始调用对象的wait方法
Thread-1已经获取到对象的锁
Thread-1开始调用对象的wait方法
Thread-0执行完成
从执行结果中可以看到:只打印了“Thread-0执行完成”,而没有打印“Thread-1执行完成”。这说明notify方法只会唤醒一个等待的线程,至于会唤醒哪一个,这个并不是由我们来决定的,而是jvm来决定。不同JDK版本的实现方式会有所不同,JDK8会唤醒等待时间最长的线程。
notifyAll():
// 使用notifyAll()唤醒所有等待的线程
public class NotifyAllWaitThreads implements Runnable{
public static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
NotifyAllWaitThreads notifyAllWaitThreads = new NotifyAllWaitThreads();
Thread thread = new Thread(notifyAllWaitThreads);
Thread thread2 = new Thread(notifyAllWaitThreads);
thread.start();
thread2.start();
Thread.sleep(100);
synchronized (lock) {
// 唤醒所有等待的线程
lock.notifyAll();
}
}
@Override
public void run() {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + "已经获取到对象的锁");
System.out.println(Thread.currentThread().getName() + "开始调用对象的wait方法");
lock.wait();
System.out.println(Thread.currentThread().getName() + "执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
Thread-0已经获取到对象的锁
Thread-0开始调用对象的wait方法
Thread-1已经获取到对象的锁
Thread-1开始调用对象的wait方法
Thread-1执行完成
Thread-0执行完成
可以看到,Thread-0和Thread-1都执行完成了。说明notifyAll()可以唤醒所有等待的线程。被唤醒的线程谁先优先执行呢?这个也不是由我们来决定的。线程被唤醒后都重新去获取对象锁,最先拿到对象锁的线程会先执行。
Thread中的方法
Thread中也有一些方法可以使线程陷入阻塞状态,如sleep()、join()。
sleep()
sleep方法是必须传参的,而且参数必须是大于等于0的long类型。如果传入的参数为0,线程会放弃cpu的执行权,并重新去获取cpu的执行权。
join()
join()方法有些特殊,在哪个线程中使用了join()方法,就阻塞哪个线程,直到调用join方法的thread执行完成。
// Thread.join()方法演示
public class ThreadJoin implements Runnable {
public static void main(String[] args) throws InterruptedException {
ThreadJoin threadJoin = new ThreadJoin();
Thread thread = new Thread(threadJoin);
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "开始执行");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果
Thread-0开始执行
Thread-0开始完毕
main开始完毕
在主线程main中使用了thread.join(),这时候主线程就会被阻塞,直到thread线程执行完成后主线程才会继续执行。
join()和wait()之间的关联
Thread的sleep()、yield()方法都是native方法,不是由java实现的,只有join()是由java实现的。
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可以看到,join()方法的阻塞线程最终调用的是wait()方法。但是没有调用notify()或者notifyAll()方法。那么为什么调用wait(0)后线程不会一直阻塞呢?那是因为当线程执行完成之后,会自动调用notify()方法。所以我们可以用另外一种方式去实现join()方法。
通过wait()方法实现join()
// 通过wait方式实现join
public class OtherWayImplJoin {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"开始执行");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
synchronized (thread){
thread.wait();
}
System.out.println(Thread.currentThread().getName()+"执行完成");
}
}
yield()
当前线程让出cpu的执行权,调用此方法不会造成线程阻塞。