【并发编程基础】线程基础(方法、状态)

思思问问

1 线程状态简述

Java线程在运行的生命周期中可能处于如下6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。

线程状态说明
NEW初始状态,线程刚被创建,但是并未启动(还未调用start方法)。
RUNNABLE运行状态,JAVA线程将操作系统中的就绪(READY)和运行(RUNNING)两种状态笼统地称为“运行中”。
BLOCKED阻塞状态,表示线程阻塞于锁。
WAITING等待状态,表示该线程无限期等待另一个线程执行一个特别的动作。
TIMED_WAITING超时等待状态,不同于WAITING的是,它可以在指定时间自动返回。
TERMINATED终止状态,表示当前状态已经执行完毕。

线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换。

<img src="https://img-blog.csdnimg.cn/20200929220833538.png" style="zoom:80%;" />

2 Thread类常用方法

2.1 start()与run()

void start()启动一个新线程,在新的线程运行run方法中的代码。

@Slf4j
public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        thread.setName("new thread");
        thread.start();
        log.debug("main thread");
    }
}

输出结果如下,可见run()方法里面内容的调用是异步的。

14:06:43.754 [main] DEBUG com.kai.demo.basic.ThreadTest - main thread
14:06:43.754 [new thread] DEBUG com.kai.demo.basic.ThreadTest - running...

将上面代码的thread.start()改为 thread.run(),输出结果如下,可见 run()方法里面内容的调用是同步的。

14:08:17.974 [main] DEBUG com.kai.demo.basic.ThreadTest - running...
14:08:17.979 [main] DEBUG com.kai.demo.basic.ThreadTest - main thread

2.2 sleep()与yield()

2.2.1 sleep()

调用 sleep()会让当前线程从 RUNNING进入TIMED_WAITING状态(超时等待)。sleep()方法导致了程序暂停执行指定的时间,让出CPU给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放锁

Tips:sleep()中指定的时间是线程暂停运行的最短时间,不能保证该线程睡眠到时后就开始立刻执行。
public class TimeWaitingTest extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if ((i) % 5 == 0) {
                System.out.println("开启线程数为:" + i);
            }
            System.out.print(i);
            try {
                Thread.sleep(1000);
                System.out.print(" 线程睡眠1秒!\n");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new TimeWaitingTest().start();
    }
}

运行结果:

开启线程数为:0
0 线程睡眠1秒!
1 线程睡眠1秒!
2 线程睡眠1秒!
3 线程睡眠1秒!
4 线程睡眠1秒!
开启线程数为:5
5 线程睡眠1秒!
6 线程睡眠1秒!
7 线程睡眠1秒!
8 线程睡眠1秒!
9 线程睡眠1秒!

可见,线程睡眠到时间就会自动苏醒,并返回到Runnable(可运行)中的就绪状态。

Tips:建议用 TimeUnit 的 sleep() 代替 Thread 的 sleep()来获得更好的可读性。

2.2.2 yield()

调用yield()静态原生方法,也能暂停当前线程,其与sleep()有何异同呢?

  • sleep(long)方法会使线程转入超时等待(TIMED_WAITING)状态,时间到了之后才会转入就绪状态。而yield()方法不能指定时间,不会将线程转入TIMED_WAITING状态,而是强制线程进入READY状态,从而让其它具有相同优先级的等待线程获取执行权。
  • 使用sleep(long)方法需要处理异常InterruptedException ,而yield()不用处理异常。

sleep()与yield()的共同点就是:

  • 两者都不释放锁、释放CPU资源

2.3 wait()

线程中调用Object.wait()方法时,当前线程就会进入阻塞状态,会释放CPU资源和释放同步锁(类锁和对象锁)。直到其他线程调用此对象的notify()方法或 notifyAll()方法。

wait是Object类中的方法,方法内部很简单,使用native方法实现:

    // 进入WAITING状态
    public final void wait() throws InterruptedException {
        wait(0);
    }
    // 进入TIMED_WAITING状态
    public final native void wait(long timeout) throws InterruptedException;

    // 进入TIMED_WAITING状态
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }
        if (nanos > 0) {
            timeout++;
        }
        wait(timeout);
    }    

wait()方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

如果当前线程在等待之前或在等待时被任何线程中断,则会抛出 InterruptedException 异常。

使用案例:

public class WaitingTest {
    public static Object obj = new Object();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (obj) {
                        try {
                            System.out.println(Thread.currentThread().getName() + "=== 调用wait方法,进入WAITING状态,释放锁对象");
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "=== 从WAITING状态醒来,获取到锁对象,继续执行");
                    }
                }
            }
        }, "线程A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) { //每隔3秒 唤醒一次
                    try {
                        System.out.println(Thread.currentThread().getName() + "‐‐‐ 等待3秒钟");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj) {
                        System.out.println(Thread.currentThread().getName() + "‐‐‐ 获取到锁对象, 调用notify方法,释放锁对象");
                        obj.notify();
                    }
                }
            }
        }, "线程B").start();
    }
}

执行结果:

线程A=== 调用wait方法,进入WAITING状态,释放锁对象
线程B‐‐‐ 等待3秒钟
线程B‐‐‐ 获取到锁对象, 调用notify方法,释放锁对象
线程B‐‐‐ 等待3秒钟
线程A=== 从WAITING状态醒来,获取到锁对象,继续执行
... ... 

通过上述案例我们会发现,线程A在调用了某个对象的 Object.wait 方法后,需要等待线程B调用此对象的
Object.notify()方法将其唤醒。

2.4 join()

join()是Thread类中的方法,若没有指定时间,当前线程中调用另一个线程的join时,当前线程会挂起,等到另一个线程执行完毕之后才能继续向下执行;若指定了时间,到等待的指定时间,即便调用join的线程没运行完run方法,当前线程也会继续往下运行。

先来看案例1

public class JoinTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[" + Thread.currentThread().getName() + "] running");
            }
        }, "child");
        
        thread.start();
        try {
            thread.join();
            if (thread.isAlive()) {
                System.out.println("[" + Thread.currentThread().getName() + "] child thread has not finished");
            } else {
                System.out.println("[" + Thread.currentThread().getName() + "] child thread has finished");
            }
            System.out.println("[" + Thread.currentThread().getName() + "] main thread has finished");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

[child] running
[main] child thread has finished
[main] main thread has finished

值得注意的是,案例1中child线程中并没有调用notify()或者notifyAll()方法,main线程是如何被唤醒的呢?下面我们就来一探究竟。

查看源码可知,join()也是通过调用wait()方法实现的,所以说调用join方法的当前线程也会释放锁与CPU资源,等待被唤醒。在案例1中,主线程调用子线程对象的join()方法,因此主线程在此位置需要释放锁,并进行等待。

    public final void join() throws InterruptedException {
        join(0);// 调用wait(0)方法,进入WAITING状态
    }

    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);// 调用wait(0)方法,进入WAITING状态
            }
        } else {
            while (isAlive()) {// 判断线程是否存活
                long delay = millis - now;
                if (delay <= 0) {
                    break;// 跳出join()
                }
                wait(delay);// 调用wait(long),进入TIMED_WAITING状态
                now = System.currentTimeMillis() - base;
            }
        }
    }
    public final synchronized void join(long millis, int nanos) throws InterruptedException {
        ---Omit---
    }

查看isAlive()方法源码:

    /**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     * 测试线程是否存活(线程已经开始,还没有死亡的状态即为存活)。
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */****
    public final native boolean isAlive();

可见,当main线程调用child线程的join方法时,其实就相当于调用child线程的wait方法,然后使main线程处于阻塞状态。通过debug,发现在child线程执行完后,系统会自然地调用Thread类中的exit()方法,查看源码:

     /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     * 此方法由系统调用,当该线程完全退出前给它一个机会去释放空间。
     */
private void exit() {
        if (group != null) {                // 线程组在Thread初始化时创建,存有创建的子线程
            group.threadTerminated(this);   // 调用threadTerminated()方法
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

此时线程组中child子线程依然存在,因此会调用线程组的threadTerminated()方法。 查看ThreadGroup.threadTerminated()方法源码:

     /**
     * Notifies the group that the thread {@code t} has terminated.
     * 通知线程组,t线程已经终止。
     */
    void threadTerminated(Thread t) {
        synchronized (this) {
            remove(t);                                        // 从线程组中删除此线程

            if (nthreads == 0) {                            // 当线程组中线程数为0时
                notifyAll();                                // 唤醒所有待定中的线程
            }
            if (daemon && (nthreads == 0) &&
                (nUnstartedThreads == 0) && (ngroups == 0))
            {
                destroy();
            }
        }
    }

可见,正是通过此threadTerminated方法,将child线程从线程组中删除,释放占用的资源(锁),唤醒其他等待的线程。也就是说,在child线程执行结束后,等待状态的主线程才被唤醒,并打印“main thread has finished”。

最后调用threadTerminated()的解释其实是==存疑==的。照理来讲,由于主线程的存在,线程组中线程数不会为0,更不会调用notifyAll()

stackoverflow上关于此问题的回答有:

The notify() for this is handled by the Thread subsystem. When the run() method finishes, the notify() is called on the Thread object.

也就是说,join()调用wait()之后,notify()由本机线程系统内的代码唤醒,当run()方法完成时,就会在Thread对象上调用notify()。

OpenJDK 8为例,我们来查看/jdk8/hotspot/file/tip/src/share/vm/runtime/thread.cpp,看是否有唤醒join()的方法:

void JavaThread::run() {
  ...  
  // We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner();
}

void JavaThread::thread_main_inner() {
  assert(JavaThread::current() == this, "sanity check");
  assert(this->threadObj() != NULL, "just checking");

  // Execute thread entry point unless this thread has a pending exception
  // or has been stopped before starting.
  // Note: Due to JVM_StopThread we can have pending exceptions already!
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {
    HandleMark hm(this);
    this->entry_point()(this, this);
  }

  DTRACE_THREAD_PROBE(stop, this);

  this->exit(false);
  delete this;
}

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  ...
  // 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).
  ensure_join(this);
  ...
}

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);
  lock.notify_all(thread);
    
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

可见,thread.cpp中是通过lock.notify_all(thread)唤醒join()线程的。

也就是说,当main线程中调用child线程的join()方法(相当于wait(0)方法)时,main线程会释放锁与CPU资源,进入WAITING状态,child线程运行;当child线程执行结束后,由thread.cpp中的notify_all()唤醒main线程,main线程获取到锁与CPU资源后继续执行

捋清这层逻辑后,我们再来看案例2

public class JoinTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[" + Thread.currentThread().getName() + "] begins");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("[" + Thread.currentThread().getName() + "] ends");
            }
        }, "child");
        
        thread.start();// 启动child线程
        try {
            thread.join(1000);
            if (thread.isAlive()) {
                System.out.println("[" + Thread.currentThread().getName() + "] child thread has not finished");
            } else {
                System.out.println("[" + Thread.currentThread().getName() + "] child thread has finished");
            }
            System.out.println("Join Success");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

[child] begins
[main] child thread has not finished
Join Success
[child] ends

案例2中,main线程中开启了一个child线程,child线程启动成功调用Thread.sleep(5000),进入TIMED_WAITING状态;之后main线程再调用thread.join(1000),也进入TIMED_WAITING状态。与案例1不同的是,child线程与main线程都处于TIMED_WAITING状态,而且child线程在TIMED_WAITING状态保持的时间更长。照理之前的分析,只有等child线程执行完后,由thread.cpp中的lock.notify_all()唤醒main线程。那么结果应该如下:

[child] begins
[child] ends
[main] child thread has finished
Join Success

==为什么实际输出结果不是这样呢==?其实问题关键于在join()方法上。

再来看看join调用wait方法的源码:

        if (millis == 0) {
            while (isAlive()) {// 判断线程是否存活
                wait(0);// 调用wait(0)方法,进入WAITING状态
            }
        } else {
            while (isAlive()) {// 判断线程是否存活
                long delay = millis - now;
                if (delay <= 0) {
                    break;// 跳出join()
                }
                wait(delay);// 调用wait(long),进入TIMED_WAITING状态
                now = System.currentTimeMillis() - base;
            }
        }
    }

可见,只要while()中判断调用join方法的对象保持存活状态(True),就可能会循环调用wait(0)/wait(long)方法。

为什么要设计循环调用wait呢?

实际上,线程是可能没有接收通知、中断或超时的情况下被唤醒,这就是所谓的假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试线程被唤醒的条件来防止这种情况发生。也就是说,系统不一定在子线程结束后才调用notifyAll()

案例1中进入while (isAlive()){wait(0);}循环。当主线程执行到thread.wait(0)之后,主线程让出当前的thread对象的锁,代码不再继续往后运行,while()不再进行循环。主线程的程序计数器记录当前线程执行代码的行数,系统调用notifyAll()之后,主线程进入该对象的锁池,竞争该对象锁。拿到对象锁之后,继续从刚刚wait(0)之后的代码执行,即继续执行while循环,子线程是否存活。如果不满足条件,就会继续执行循环,直到下一次notifyAll()调用。

在案例2中,当进入while循环时,delay>0,调用的wait(delay);。主线程让出当前的thread对象的锁,代码不再继续往后运行,while()不再进行循环。与案例1不同的是,在系统调用notifyAll()之后,由于子线程仍处于TIMED_WAITING状态,子线程仍存活,会继续执行while循环。当第二次进入循环时,由于时间设定,delay<0,直接跳出循环,打印child thread has not finished

2.5 interrupt()

线程的thread.interrupt()方法用于中断线程,他会设置该线程的中断状态位为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

(1)判断线程是否被中断

    // 测试当前线程是否已经中断。掉用后线程的中断状态由该方法清除。也就是说,连续两次调用判断中断线程,则第二次调用将返回false。
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    // 测试线程是否已经中断。线程的中断状态不受该方法的影响。也就是说,连续两次调用判断中断线程,则第二次仍返回true。
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

(2)如何中断线程

interrupt()的作用是即是中断本线程。查看源码:

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

    private native void interrupt0();

本线程中断自己是被允许的,其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。

  • 中断非阻塞状态线程

打断正常运行的线程, 线程并不会暂停。可以调用方法Thread.currentThread().isInterrupted()的值是来手动停止线程。

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("[" + Thread.currentThread().getName() + "] running");
                    long time = System.currentTimeMillis();
                    // 使用while循环模拟 sleep
                    while ((System.currentTimeMillis() - time < 1000)) {
                    }
                }
            }
        }, "child");

        t2.start();
        Thread.sleep(3000);
        System.out.println("[" + Thread.currentThread().getName() + "] child是否被打断?" + t2.isInterrupted());
        System.out.println("Asking thread to stop...");
        t2.interrupt();
        System.out.println("[" + Thread.currentThread().getName() + "] child是否被打断?" + t2.isInterrupted());
    }
}

执行结果:

[child] running
[child] running
[child] running
[child] running
[main] child是否被打断?false
Asking thread to stop...
[main] child是否被打断?true
  • 中断阻塞状态线程

wait()/wait(long)/wait(long, int),join()/join(long)/join(long, int),sleep(long)/sleep(long, int)方法都会让线程进入阻塞状态。若线程在阻塞状态时调用了interrupt()方法打断,那么它的“中断状态”会被清除,并且会收到一个InterruptedException异常。

以下面代码为例,线程通过sleep(long)进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”;同时,会产生一个InterruptedException的异常。

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[" + Thread.currentThread().getName() + "] start");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    System.out.println("InterruptedException --- [" + Thread.currentThread().getName() + "] Interrupted");
                }
            }
        }, "child");

        t1.start();
        Thread.sleep(100);
        System.out.println("[" + Thread.currentThread().getName() + "] child是否被打断?" + t1.isInterrupted());
        t1.interrupt();
        System.out.println("[" + Thread.currentThread().getName() + "] child是否被打断?" + t1.isInterrupted());
        Thread.sleep(100);
        System.out.println("[" + Thread.currentThread().getName() + "] child是否被打断?" + t1.isInterrupted());
    }
}

执行结果:

[child] start
[main] child是否被打断?false
[main] child是否被打断?true
InterruptedException --- [child] Interrupted
[main] child是否被打断?false
  • 注意

在看了上面几个案例后,极容易产生这样一种误解——认为调用interrupt方法会中断线程。这种想法是错误的。

其实,Java的中断是一种协作机制。调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。每个线程都有一个boolean的中断状态(这个状态不在Thread的属性上),interrupt方法仅仅只是将该状态置为true。比如对正常运行的线程调用interrupt()并不能终止它,只是改变了interrupt标示符
一般说来,如果一个方法声明抛出InterruptedException,表示该方法是可中断的,比如wait/sleep/join。也就是说可中断方法会对interrupt调用做出响应(例如sleep响应interrupt的操作包括清除中断状态,抛出InterruptedException),异常都是由可中断方法自己抛出来的,并不是直接由interrupt方法直接引起的。
正是如此,Object.wait()/Thread.sleep()/Thread.join()方法,才会不断的轮询监听 interrupted 标志位,发现其为true后,会停止阻塞并抛出 InterruptedException异常

(3)总结

Thread.interrupt()方法不会真正地中断一个正在运行的线程。它主要用于设置线程的中断标示位,在线程受到阻塞的地方(如Thread.sleep()、Thread.join()、Object.wait()检查到线程为“中断状态”后)抛出一个InterruptedException异常,并且“中断状态”也将被清除,这样线程就得以退出阻塞的状态。

3 线程优先级

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

查看源代码:

    private int priority;
    
    // 设置优先级
    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
    // 获取优先级
    public final int getPriority() {
        return priority;
    }

    public String toString() {
        ThreadGroup group = getThreadGroup();
        if (group != null) {
            return "Thread[" + getName() + "," + getPriority() + "," +
                           group.getName() + "]";
        } else {
            return "Thread[" + getName() + "," + getPriority() + "," +
                            "" + "]";
        }
    }

    // 优先级常量
     public final static int MIN_PRIORITY = 1;
     public final static int NORM_PRIORITY = 5;
     public final static int MAX_PRIORITY = 10;

查看Thread 初始化 priority 的源代码:

        Thread parent = currentThread();  
        // 获取父线程的线程优先级
        this.priority = parent.getPriority();

可见,子线程默认优先级和父线程保持一致,Java主线程默认的优先级是 5。

public class PriorityTest {
    public static void main(String[] args) {
        Thread.currentThread().setPriority(3);// 主线程优先级修改为3
        Thread thread = new Thread();
        System.out.println(thread.getPriority());// 子线程优先级跟主线程优先级保持一致,并不是默认的5,而是3。
    }
}

下面通过一个例子来测试一下高优先级与低优先级差别:

public class PriorityTest {
    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    public static void main(String[] args) throws Exception {
        List<Task> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Task task = new Task(priority);
            tasks.add(task);
            Thread thread = new Thread(task, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        for (Task task : tasks) {
            System.out.println("Task Priority:" + task.priority + " Count:" + task.taskCount);
        }
    }

    static class Task implements Runnable {
        private int priority;
        private long taskCount;

        public Task(int priority) {
            this.priority = priority;
        }

        @Override
        public void run() {
            while (notStart) {
                Thread.yield();
            }
            while (notEnd) {
                Thread.yield();
                taskCount++;
            }
        }
    }
}

执行结果:

Task Priority:1 Count:12774
Task Priority:1 Count:12775
Task Priority:1 Count:12762
Task Priority:1 Count:12747
Task Priority:1 Count:12758
Task Priority:10 Count:1984746
Task Priority:10 Count:1984810
Task Priority:10 Count:1982704
Task Priority:10 Count:1985309
Task Priority:10 Count:1980892

可见,高优先级的线程大概率比低优先的线程优先获得 CPU 资源

4 守护线程

一般来说,Java 中的线程可以分为两种:守护线程和普通线程。 JVM 刚启动时创建的所有线程,除了主线程(main thread)外,其他的线程都是守护线程(比如:垃圾收集器、以及其他执行辅助操作的线程(例如Tomcat中的Acceptor和Poller线程))。

当创建一个新线程时,新线程将会继承它线程的守护状态,默认情况下,主线程创建的所有线程都是普通线程

在JVM中,所有普通线程执行完毕后,无论有没有守护线程,虚拟机都会自动退出。因此,JVM退出时,不必关心守护线程是否已结束,所有仍然存在的守护线程都将被抛弃,既不会执行 finally 部分的代码,也不会执行 stack unwound操作,JVM直接退出。

示例如下:

public class DaemonTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
        thread.setDaemon(true);// 调用setDaemon(true)把该线程标记为守护线程
        thread.start();
    }

    static class DaemonRunner implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                System.out.println("DaemonThread finally run ...");
            }
        }
    }
}

运行DaemonTest程序后,可以看到在终端或者命令提示符上没有任何结果输出。main线程(非Daemon线程)在启动了线程DaemonRunner之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有运行中的普通线程了,虚拟机立刻退出。Java虚拟机中的所有Daemon线程都立即终止,DaemonRunner中的finally块也就没有执行。

可见,我们在日常编码中要注意,守护线程不能持有任何需要关闭的资源,例如打开文件等。因为虚拟机退出时,守护线程没有任何机会来关闭文件,这容易导致数据丢失。

参考资料

多线程基础

JAVA并发编程的艺术

Thread的中断机制(interrupt)

Java Thread的join() 之刨根问底

阅读 546

我思
Stay Hungry, Stay Foolish!

Stay Hungry, Stay Foolish!

24 声望
3 粉丝
0 条评论

Stay Hungry, Stay Foolish!

24 声望
3 粉丝
文章目录
宣传栏