Java多线程(二):线程的相关操作
2.1 线程的生命周期
2.1.1 新建和就绪
- 新建状态:new一个线程对象后,就是新建状态。
注意:这里只是创建了一个Thread对象,就是一个普通的Java对象,没有表现出任何线程的动态特征,因此这里并非创建新线程。
- 就绪状态:当调用start()后,才真正地创建新线程,系统内把run()当作线程体,表示该线程可以运行了,进入就绪状态。
注意:如果直接调用run(),并不能启动线程,这时的run()就是一个普通的Java方法,并且在该方法中不能直接使用Thread的相关方法;并且此时该线程已经不再处于新建状态,不能再次调用start()。
2.1.2 运行和阻塞
2.1.3 死亡
- 进入死亡状态的方式:
- run()或call()方法执行完,线程正常结束。
- 下层抛出未捕获的异常或错误。
- 直接调用Thread.stop()方法结束线程:该方法会直接终止线程,并立即释放这个线程持有的所有锁,因此可能会导致数据不一致问题,已废弃。
- 线程死亡是不可逆的。
- 安全地终止线程的两种方式:
- 使用boolean变量。
- 使用中断。
范例:
public class ShutDown{
private static class Run implements Runnable{
private long i;
private boolean flag = true;
@Override
public void run() {
//方式1:利用Boolean变量
//方式2:利用中断
while(flag && !Thread.currentThread().isInterrupted()){
i++; //正常执行线程逻辑。
}
System.out.println("count i = " + i);
}
public void calcle(){
flag = false;
}
}
public static void main(String[] args) throws Exception {
Run one = new Run();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
TimeUnit.SECONDS.sleep(1);
//方式2:利用中断
countThread.interrupt();
//count i = 785844152
Run two = new Run();
countThread = new Thread(two, "CountThread");
countThread.start();
TimeUnit.SECONDS.sleep(1);
//方式1:利用Boolean变量
two.calcle();
//count i = 791844688
}
}
2.2 后台线程
- Thread类:
void setDaemon(boolean on); //on为true时,将该线程设置为守护线程(必须在该线程start之前,否则会引发异常)。
boolean isDaemon(); //判断是否为守护线程。
- 又称守护线程、精灵线程,它的任务是为其他线程提供服务。JVM的垃圾回收线程就是典型的后台线程。
- 如果所有的前台线程都死亡,后台线程会自动死亡。
- 当虚拟机中只剩下后台线程时,虚拟机退出。
2.3 线程的休眠与谦让、挂起与继续执行
2.3.1 sleep()休眠与yield()谦让
- 两者均是Thread类的静态方法:
static void sleep(long millis);
static void sleep(long millis, int nanos);
static void yield();
- 比较
- sleep()暂停线程后,会给其他线程执行机会,不理会其他线程的优先级;yield()只会给优先级大于等于自身的线程机会。二者均不会释放锁。
- sleep()会将线程转入阻塞状态,阻塞时间结束后进入就绪状态;yield()是将线程转入就绪状态,完全有可能刚被yield()暂停,又立即获得执行机会。
- sleep()声明抛出了异常,所以调用时要么捕获该异常,要么显式抛出,而yield()没有。
- sleep()有更好的可移植性,推荐使用。
2.3.2 suspend()挂起与resume()继续执行
- 两者均是Thread类的方法
void suspend();
void resume();
- suspend()暂停线程后不会释放任何资源,直到其他线程上执行了resume(),被挂起的线程才能继续执行。但是如果意外地,resume()在suspend()之前执行了,那么被挂起的线程就很难有机会再继续执行。
- 已弃用。
2.4 线程的优先级
- Thread类中的方法和静态常量:
void setPriority(int newPriority); //newPriority范围是1~10,数字越大优先级越高,也可以用下面的三个常量。
static int MAX_PRIORITY; //线程可以拥有的最大优先级,10。
static int MIN_PRIORITY; //线程可以拥有的最小优先级,1。
static int NORM_PRIORITY; //分配给线程的默认优先级,5。
- 优先级高的线程并不一定在低优先级线程之前执行,还和OS有关。
2.5 线程的中断
- 线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程。至于目标线程接到通知后如何处理,完全由目标线程自行决定。线程通过检查自身是否被中断来进行响应。
- Thread类中方法:
public void interrupt(); //中断线程,即设置中断标记位。
public boolean isInterrupted(); //判断是否被中断。
public static boolean interrupted(); //判断是否被中断,并清除中断标志位。
- 中断异常:InterruptedException:大部分迫使线程等待的方法均会抛出该异常,该异常不是运行时异常,即程序必须捕获并处理它,并且会清除中断标志位。总结如下:
public static native void Thread.sleep() throws InterruptedException; //会清除中断标记位,为了捕捉到中断,需要在异常处理中再次设置中断标记位。
public final void Object.wait() throws InterruptedException;
public final void Thread.join() throws InterruptedException;
public static native Thread.yield(); //不响应中断。
public void Condition.await() throws InterruptedException;
public void Condition.awaitUninterruptibly(); //不响应中断。
public void Semaphore.acquire() throws InterruptedException;
public void Semaphore.acquireUninterruptibly(); //不响应中断。
public void CountDownLatch.await() throws InterruptedException;
public void CyclicBarrier.await() throws InterruptedException ,BrokenBarrierExceptiion;
public static void LockSupport.park(); //响应中断,但不抛出InterruptedException异常,且可以获得中断标记位。