概述
深入的介绍了JDK源码中,Thread对象的几个常用细节点,以加深对其内部原理的认识。夯实读者Java基础知识。
分析
基本介绍
Thread是Java中重要的一个类,它是实现多线程的重要成员,也是多线程的开启者。每一个线程都有优先级,优先级高的线程通常是先于优先级低的线程执行。
每一个线程可以被标注成守护线程(Java中线程分为守护线程和非守护线程,当非守护线程执行完毕后,只剩下守护线程时,虚拟机会退出应用)。默认情况下,开启的线程
为非守护线程。线程的开启为start方法,开启线程后,执行的代码为run方法,需要override run方法。有两种实现线程的方式,一是:继承Thread类,
另一种是实现Runnable接口,例如:class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } PrimeThread p = new PrimeThread(143); p.start(); class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } PrimeRun p = new PrimeRun(143); new Thread(p).start();
新启一个线程执行其run()方法,一个线程只能start一次。主要是通过调用native start0()来实现。run()方法是不需要用户来调用的,
当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。\
每个线程都有自己的名字,多个线程可以有相同的名字。public final synchronized void setName(String name) { checkAccess(); if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; if (threadStatus != 0) { setNativeName(name); } } /** * Returns this thread's name. * * @return this thread's name. * @see #setName(String) */ public final String getName() { return name; }
可以改变或获取线程的名称
主要介绍
1.线程优先级
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
最低为1,最高为10,默认优先级为5. 优先级的设置和获取如下:
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); } } /** * Returns this thread's priority. * * @return this thread's priority. * @see #setPriority */ public final int getPriority() { return priority; }
2.线程状态
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
分别是创建状态(未开始)、运行状态、线程阻塞状态(阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
通常由Object.wait发起,目的等待重新获取锁执行同步代码块)、线程等待状态(可以有三种方式实现:
Object.wait、Thread.join和LockSupport.park,一个线程的等待状态,是为了等待另一个线程执行某种特别的操作。Object.wait是等待其他线程执行
Object.notify()或者Object.notifyAll()去唤醒,Thread.join()目的是等待线程执行完毕)、定时等待状态(可以有:Thread.sleep、Object.wait(long)
Thread.join(long)、LockSupport.parkNanos和LockSupport.parkUntil实现。)和线程结束状态(执行完毕)。public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus); }
需要注意的是:\
阻塞:当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。
它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。\阻塞的情况分三种: \
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 \
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 \
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、
join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。\等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,
可响应中断。例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。\
需要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,
而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但话又说回来,虽然等锁时进入的状态不一样,
但被唤醒后又都进入runnable态,从行为效果来看又是一样的。\
sleep方法:sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,
则即使调用sleep方法,其他线程也无法访问这个对象。\
yield方法:调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。
但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会,
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。\
join方法:join()、join(long millis)和join(long millis,int nanoseconds)。join()实际是利用了wait(),
只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。\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; } } } public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); } public final void join() throws InterruptedException { join(0); } public final native void wait(long timeout) throws InterruptedException;
3.守护线程和非守护线程的设置
public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; } /** * Tests if this thread is a daemon thread. * * @return <code>true</code> if this thread is a daemon thread; * <code>false</code> otherwise. * @see #setDaemon(boolean) */ public final boolean isDaemon() { return daemon; }
其中void setDaemon(boolean on)必须在线程start之前调用。否则会抛出IllegalThreadStateException异常
4.线程组
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
线程组在开启线程时将当前线程加入组中。
5.其他注意点
- stop()可以立即将一个线程终止,非常方便。但是stop被废弃,原因是stop方法太暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。
线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦!至于目标线程接到通知后如何处理,则完全由目标线程自行
决定。这点很重要,如果中断后,线程立即无条件退出,就会遇到stop同样的问题。public void interrupt() // 线程中断 public boolean isInterrupted() // 判断是否被中断 public static boolean interrupted() // 判断是否被中断,并清除当前中断状态
挂起(suspend)和继续执行(resume)线程:这两个操作是一对相反的操作,被挂起的线程,必须要等到resume后,才能执行。他们也是被废弃的方法。
- 不推荐suspend是因为suspend在导致线程暂停的同时,并不会去释放任何资源。此时,其他任何线程想要访问被他暂用的锁时,都会被牵连,导致无法
正常执行。直到对应的线程进行了resume操作,线程才能继续执行。但是,如果resume操作意外地在suspend之前执行了,那么背挂起的线程可能很难有机会
被继续执行。更重要的是它的锁不会释放,因此可能导致整个系统工作不正常。而且,对于被挂起的线程,从它的状态上看,居然是runnable。
总结
- 线程状态
线程有五个状态:new一个线程对象后,首先进入创建状态,执行Thread#start方法之后,进入就绪状态,
线程调度程序将就绪状态的线程标记为运行状态并真正运行。线程运行过程中,可以挂起,进入阻塞状态,阻塞状态恢复后,接着进入就绪而不是立马又恢复运行状态,
线程执行完了,就进入结束/死亡状态。 - Thread使用注意
线程执行的业务逻辑,放在run()方法中,使用 thread.start() 启动线程。
wait方法需要和notify方法配套使用;
守护线程必须在线程启动之前设置;
如果需要等待线程执行完毕,可以调用 join()方法。