Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。
线程中各种状态的转换关系如下图:
这三个方法都是Object
上的方法, 只有获取到了所调用对象的monitor
锁才能进行调用。是什么意思呢,举个例子:
public class WaitT {
public Object lock = new Object();
public void testWait() {
synchronized (lock) {
try {
lock.wait(1000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final WaitT waitT = new WaitT();
new Thread(() -> {
waitT.testWait();
}).start();
}
}
上述例子中,只有获取到了lock
对象的monitor
锁(通过synchronize)才能调用lock.wait
(注意,这里是通过lock对象才能调用wait,因为是获取的lock对象的锁)
JDK中一共提供了三种wait方法:
wait方法的使用必须在同步的范围内(获得monitor的锁),否则就会抛出IllegalMonitorStateException
异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll
方法的唤醒,或等待超时后自动唤醒。
既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll 的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而n6otifyAll则唤醒所有的线程
这三个方法是Thread
上的方法
sleep方法的作用是让当前线程暂停指定的时间(毫秒),sleep方法是最简单的方法,在上述的例子中也用到过,比较容易理解。唯一需要注意的是其与wait方法的区别。最简单的区别是,wait方法依赖于同步,而sleep方法可以直接调用。
而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则会释放锁。通过sleep方法实现的暂停,程序是顺序进入同步块的,只有当上一个线程执行完成的时候,下一个线程才能进入同步方法,sleep暂停期间一直持有monitor对象锁,其他线程是不能进入的.
这个又怎么理解呢,还是用具体的例子来说明:
public class SleepT {
Object lock = new Object();
public void sleepSyn() {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " sleep start");
Thread.sleep(3000L);
System.out.println(Thread.currentThread().getName() + " sleep end");
} catch (InterruptedException e) {}
}
}
public static void main(String[] args) {
SleepT sleepT = new SleepT();
new Thread( () -> sleepT.sleepSyn(), "thread1").start();
sleepT.sleepSyn();
}
}
不管是那个线程先进入sleepSyn
, 都会把threadName sleep start | threadName sleep end
打印完成后,才会让下一个线程访问,也就是说当持有对象锁的时候,sleep
期间是不会释放的
join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。
join也有三种调用方式:
void join()
void join(long millis)
void join(long millis, int nanos)
join的源码如下:
public final void join() throws InterruptedException {
join(0);
}
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呢,这个问题最开始困扰了我很久。我的理解是:
发起join调用的线程等待join的线程执行完了之后才会执行
有一些绕口,还是用一个例子来理解:
public class JoinT {
public void print() {
System.out.println("sun thread run");
}
public static void main(String[] args) {
System.out.println("main thread start");
JoinT joinT = new JoinT();
Thread sunThread = new Thread(() -> joinT.print());
sunThread.start();
try {
sunThread.join();
} catch (InterruptedException e) {}
System.out.println("main thread end");
}
}
main thread 会等待 sunThread 执行完了之后在执行。从join的源码可以看到,发起join调用实际上等于获取了sunThread
的monitor锁后,调用了sunThread.wait
,需要等待sunThread.notify(notifyAll)
才能唤醒。但是我们并没有看到唤醒的代码。这是因为当 Thread 退出后(Thread.exit), cpp的源码会自动执行 Thread.notifyAll
。所以就能理解,为什么join线程执行完成后,调用join的线程会被唤醒执行
yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态
yield是一种线程让步,暂时让出时间片,但是下一次时间片同样有机会抢占