线程的概念
线程是进程的子任务,一个进程可以创建多个线程,线程不拥有系统资源,但是线程可以共享进程的资源,而线程自己也有一块独立的小块空间:包括堆栈,程序计数器和局部变量。 线程是CPU调度和分派的基本单位。在同一时刻cpu只能执行一段代码,或者说叫一段顺序执行流,也就是线程。cpu在不同的线程之间来回切换,因为cpu的运行速度非常高,看起来就像这些线程一起执行一样,这就是并发。
线程状态
从不同的角度,会有不同的结果。 说到线程的状态,网上的说法众说纷纭,有说5种的,有说6种的,还有说7种的。具体怎么回事呢,得看是从什么角度来看。、 从操作系统来看: 线程可以分为5个状态
- 新建状态(NEW)
线程的初始状态,还没有调用线程的start()方法 - 就绪状态(READY)
线程启动之后,就进入就绪状态,等待cpu分配时间片执行。处于就绪状态的线程,只是说线程做好了cpu随时调用执行的准备 - 执行状态(RUNNING)
cpu开始调度执行处于就绪状态的线程时,线程就是执行状态了。要想线程进入执行状态,它必须先是就绪状态 - 阻塞状态(BLOCKED)
当处于运行状态的线程,由于某些原因暂时放弃cpu的使用权,则处于阻塞状态。直到其进入就绪状态,cpu才可以重新调度执行它。 - 终止状态(TERMINATED)
线程执行完成或异常退出,线程终止。
而Java中,线程则分了6种状态。 新建和终止状态跟上面一样,就绪和执行状态,Java线程将这两种合并为一种:RUNNABLE:运行状态,表示线程已经在虚拟机中执行了。而阻塞状态,Java将之细分了3个状态: BLOCKED,WAITING,TIMEED_WAITING。 在Thread.State枚举中列举了这6种状态:
- 新建状态(NEW)
Thread 创建之后的初始状态,还未调用start()方法,只是内存中的一个对象,操作系统并没有创建线程。 - 运行状态(RUNNABLE)
线程已经在java虚拟机中执行,可能cpu调度已经正式开始运行,也有可能等待cpu调度。 - 阻塞状态(BLOCKED)
在Java中,BLOCKED 状态跟内置锁synchronized(也叫监视器锁)关联,线程在等待获取其它线程持有的监视器锁进入同步代码块时,线程处于BLOCK状态。或者在线程调用wait方法之后等待重新进入同步代码块,也处于BLOCK状态。BLOCK只跟监视器锁有关,如果不需要进入同步代码块,而是因为其它的原因线程需要等待,不属于BLOCK. - 等待状态(WAITING )
一个线程由于调用了以下方法之一: Object.wait(),Thread.join(),LockSupport.park()而处于等待状态。 处于WAITING状态的线程在等待其它线程执行特定的行为。例如: 一个线程A调用了某个对象上的wait()方法,需要等待其它线程去执行对象上的notify()或者notifyAll()方法,线程A才会结束等待。 - 限时等待(TIMED_WAITING)
一个线程由于调用了以下方法之一: Thread.sleep(long),Object.wait(long),Thread.join(long),LockSupport.parkNanos(long),LockSupport.parkUtil(long) 而等待。从这个命名上也说明是限时等待,不会无限等待下去,特定时间之后即使它等待的事件没有发生,也会退出等待状态继续执行。 终止状态(TERMINATED) 线程已经终止,包括正常执行完成或者异常退出。
线程通信: wait, notify, notifyAll, join
上面在RUNNABLE到WAITING,TIMED_WAITING状态的转换过程中,提到了wait,notify等方法。
wait
该方法是Object的一个实例方法,
/ ** @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
* /
public final void wait() throws InterruptedException {
wait(0);
}
这也表明任何对象都可以调用wait方法:此时会释放对象的监视器锁,(监视器锁是内置锁的另一种称呼,两者是一样的),并且当前线程(标记为线程T)会保持休眠 直到发生一下四种情况之一:
- 其它线程调用该对象上的notify方法,并且当前线程正好被选中唤醒
- 其它线程调用该对象上notifyAll方法
- 其它线程中断了当前线程,当前线程的中断标志也会被清除
- 调用wait时传入了一个正数的等待时间,等待的时间过去
以上四种情况发生之后,线程T结束休眠,重新进入线程调度:它会按正常的方式与其他线程竞争锁。也就是说,休眠结束后,线程的同步状态跟调用wait前完全一样。 第3种情况中,wait方法会抛出InterruptedException异常,并且将线程的中断标志清除 第4种情况中,等待的时间过后如果线程在竞争中没有获得内置锁,后续代码也是无法执行的,超时时间达到仅仅是把线程从休眠状态中移出
调用wait方法的线程必须拥有对象的监视器锁,怎么拥有呢,有3种方式: 5. 调用对象的synchronized同步方法 6. 执行锁定在这个对象上的同步代码块 7. 对于Class对象,执行该类上的静态同步方法(static synchronized methodName)
这也跟Java内置锁 synchronized里的“获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法”说法一致。 如果线程没有获得这个对象的监视器锁呢,wait方法会抛出 IllegalMonitorStateException异常。
虚假唤醒
我们调用wait时,经常是需要判断某些条件的,例如当某某资源找不到时,调用wait方法。通常会这么写
synchronized(obj){
dosomething
if( condition not satisfy){
obj.wait()
}
实际上并不推荐这么写,而是这样:
synchronized(obj){
dosomething
while( condition not satisfy){
obj.wait()
}
就是因为虚假唤醒。一个线程也有可能在没有别的线程调用notify,nofifyAll,也没有中断,等待的时间也没有到的时候被唤醒,这叫做”虚假唤醒“,虽然在实践中很少出现,但是应用也要预防这种情况的发生。
notify 和 notifyAll
这两个方法也是Object的实例方法:
public final native void notify();
public final native void notifyAll();
notify()方法唤醒一个正在该对象监视器锁的线程(调用了wait方法的线程),如果有多个线程在等待对象监视器锁,其中之一会被唤醒,具体唤醒哪个由java虚拟机决定。notifyAll()则会唤醒所有在该对象监视器上等待的线程,并且顺序是先进后出,最后一个进入休眠的线程会被首先唤醒
值得注意的是
调用notify(),notifyAll()时不会释放对象锁,而是notify所在的同步代码块结束之后才会释放锁,所以在调用notify,notifyAll方法之后,最好直接结束同步代码块。否则notify调用之后,锁不释放,被唤醒的线程也是在synchronized中的,获取不到对象锁也无法执行
notify,notifyAll 代码示例:
public static void main(String[] args) {
Object service = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("thread1 sleep 2s");
ThreadUtil.sleep(2000);
System.out.println("thread1 call wait");
try {
service.wait();
System.out.println("thread1 wait finish");
} catch (Exception e) {
System.out.println("thread1 wait throw a exception");
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("thread2 sleep 2s");
ThreadUtil.sleep(2000);
System.out.println("thread2 call wait");
try {
service.wait();
System.out.println("thread2 wait finish");
} catch (Exception e) {
System.out.println("thread2 wait throw a exception");
e.printStackTrace();
}
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("thread3 is running");
service.notify();
}
}
});
thread1.start();
thread2.start();
ThreadUtil.sleep(2100); // 确保有一个线程调用了wait方法,如果没有线程调用wait方法而陷入等待,notify方法没有意义
thread3.start();
}
执行结果:
thread1 sleep 2s
thread1 call wait
thread2 sleep 2s
thread2 call wait
thread3 is running
thread1 wait finish
thread1先执行,在调用wait之后释放对象锁进入等待,thread2获得对象锁,然后执行,也调用wait方法进入等待,thread3获得锁,调用notify方法唤醒某一个线程。thread1和thread2中一个会被唤醒,上面是thread1被唤醒:输出thread1 wait finish。也有可能是thread2被唤醒。notify只能唤醒一个线程,所以thread2的wait将会一直等待下去。 如果将thread3例的notify改为notifyAll。
thread2 sleep 2s
thread2 call wait
thread1 sleep 2s
thread1 call wait
thread3 is running
thread1 wait finish
thread2 wait finish
thread1和thread2都被唤醒了。
带时间参数的wait示例
public static void main(String[] args) {
Object service = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("thread1 call wait");
try {
service.wait();
System.out.println("thread1 wait finish");
System.out.println("thread1 sleep 10s");
ThreadUtil.sleep(10000);
} catch (Exception e) {
System.out.println("thread1 wait throw a exception");
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("thread2 call wait");
try {
service.wait(3000);
System.out.println("thread2 wait finish");
} catch (Exception e) {
System.out.println("thread2 wait throw a exception");
e.printStackTrace();
}
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("thread3 is running");
service.notify();
}
}
});
thread1.start();
thread2.start();
ThreadUtil.sleep(100);
thread3.start();
}
执行结果:
thread1 call wait
thread2 call wait
thread3 is running
thread1 wait finish
thread1 sleep 10s
// 这里等了10秒,而不是3秒
thread2 wait finish
thread2调用了wait(3000),超时时间设置为3秒。在thread3调用notify之后,唤醒的是thread1进程,然后thread1调用sleep(10s),(sleep函数不会影响内置锁)内置锁依然在thread1手上。在3秒中的时候,thread2从休眠状态醒来,但是它没有获取到内置锁,也只能等待。直到10秒后,thread1执行完毕,释放内置锁,thread2取得锁才接着执行。
wait中被中断示例
public static void main(String[] args) {
Object service = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (service) {
System.out.println("Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());
System.out.println("thread1 call wait");
try {
service.wait();
System.out.println("thread1 wait finish");
System.out.println("thread1 sleep 10s");
ThreadUtil.sleep(10000);
} catch (Exception e) {
System.out.println("thread1 wait throw a exception");
System.out.println("exception Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());
e.printStackTrace();
}
}
}
});
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
thread1.interrupt();
}
});
thread1.start();
ThreadUtil.sleep(100);
thread4.start();
}
结果:
Thread.currentThread().isInterrupted(): false
thread1 call wait
thread1 wait throw a exception
exception Thread.currentThread().isInterrupted(): false
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.hz.MainTest$1.run(MainTest.java:85)
at java.lang.Thread.run(Thread.java:748)
可以看到wait方法抛出了InterruptedException异常,并且线程的中断标志为false了。
join 方法
示例
join是Thread类的一个实例方法。 当前线程A调用另一个线程b的join()方法,会让当前线程A阻塞,直到线程b结束(包括正常结束或者异常退出)。 如果调用b.join(long)带时间参数的,则线程A会阻塞,但是如果时间达到b还没有结束,A也会结束阻塞。
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("thread1 sleep 10s");
ThreadUtil.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread1.start();
thread1.join();
System.out.println("main thread finish");
执行结果:
thread1 sleep 10s
// 等待10秒
main thread finish
主线程main会阻塞10秒,直到thread1结束,才输出 main thread finish 如果main中调用thread1.join(3000), 那主线程只会阻塞3秒。 为什么join 能达到这个效果呢。
我们以上文thread1.join()来分析一下源码
join方法源码
// 这是一个同步方法,先获得thread1的内置锁,thread1也是一个对象,也有内置锁
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()) { // 循环判断线程thread1是否还存活,存活则持续调用wait阻塞
wait(0); // 这里的wait,是thread1.wait,而且是主线程调用的thread1.wait,阻塞的是主线程,thread1线程是正常执行的
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从上面的源码中,可以发现
- join(long millis)方法的签名中,加了synchronized关键字来保证线程安全,join(long millis)最终调用的是Object对象的wait()方法,让主线程等待。这里需要注意的是,synchronized关键字实现的隐式锁,锁的是this,即thread这个对象,上例中就是thread1对象(因为synchronized修饰的join(long millis)方法是一个成员方法,因此锁的是实例对象),我们是在main线程中,调用了thread这个对象的join()方法,所以调用wait()方法时,调用的是thread这个对象的wait()方法,所以是main线程进入到等待状态中。(调用的是thread这个对象的wait()方法,这一点很重要,因为后面唤醒main线程时,需要用thread这个对象的notify()或者notifyAll()方法)
- 既然是wait,那么必然有notify或者notifyAll。但是源码里并没有调用这两个方法,在哪里调用的呢,在jvm虚拟机中!
- Java里面的Thread类在JVM上对应的文件是thread.cpp。thread.cpp文件的路径是jdk-jdk8-b120/hotspot/src/share/vm/runtime/thread.cpp
在thread.cpp文件中定义了很多和线程相关的方法,Thread.java类中的native方法就是在thread.cpp中实现的。 join方法是当目标线程执行完成之后,主线程继续执行。我们猜测,notifyAll方法是在thread的run方法执行完之后调用的,做一些收尾工作。 在JVM中,当每个线程执行完成时,会调用到thread.cpp文件中JavaThread::exit(bool destroy_vm, ExitType exit_type)方法
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
assert(this == JavaThread::current(), "thread consistency check");
// ...省略了很多代码
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
// 关键代码就在这一行,从方法名就可以推断出,它是线程在退出时,用来确保join()方法的相关逻辑的。而这里的this就是指的当前线程。
// 从上面的英文注释也能看出,它是用来处理join()相关的逻辑的
ensure_join(this);
-
可以看到,主要逻辑在ensure_join()方法中,接着找到ensure_join()方法的源码,源码如下 -
`static void ensure_join(JavaThread* thread) { // We do not need to grap the Threads_lock, since we are operating on ourself. Handle threadObj(thread, thread->threadObj()); assert(threadObj.not_null(), “java thread object must exist”); ObjectLocker lock(threadObj, thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); // Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED. java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED); // Clear the native thread instance - this makes isAlive return false and allows the join() // to complete once we’ve done the notify_all below java_lang_Thread::set_thread(threadObj(), NULL); // 核心代码在下面这一行 // notify_all()方法肯定就是用来唤醒。这里的thread对象就是我们demo中的线程thread1这个实例对象 lock.notify_all(thread);
既然这样,如果我提前将调用thread1的notifyAll()方法呢,那不就可以让主线程提前退出阻塞了么。于是我尝试了下面的代码
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("thread1 sleep 10s");
ThreadUtil.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
ThreadUtil.sleep(1000);
synchronized (thread1) {
try {
System.out.println("call notify");
thread1.notifyAll();
System.out.println("after notify");
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thread1.start();
thread2.start();
thread1.join();
System.out.println("main thread finish");
}
结果:
Connected to the target VM, address: '127.0.0.1:58744', transport: 'socket'
thread1 sleep 10s
call notify
after notify
Disconnected from the target VM, address: '127.0.0.1:58744', transport: 'socket'
// 这句话依然是10秒钟后输出的
main thread finish
我在thread2中先停1秒钟,然后调用thread1.notifyAll(),是希望在1秒钟后,主线程能结束阻塞,继续执行。但是不起作用! why? 再来看源码里,有这一句
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
用的是while(isAlive()),而不是 if(isAlive()),thread1并没有结束,所以会再次调用wait阻塞。而且由于“虚假唤醒”的存在,调用wait时一般都是用while循环判断是否需要调用wait的,如下。
synchronized(obj){
while (! condition){
obj.wait()
}
}
那我能不能把它改成if来验证呢,我本来想创建一个新的Thread子类,覆盖join方法,但是join方法是一个final方法,无法覆写,jdk也是考虑到这种情况吧
public final synchronized void join(long millis)
|