2. Java线程基础
一、 创建新线程
1.1 继承Thread类
Thread t1 = new Thread(); t1.start();
start()方法可以创建一个新的线程,并让这个线程执行run()方法
Thread t2 = new Thread(); t2.run()
这种直接用run() 方法,不会创建一个新的线程,只是在当前线程中串行的执行run方法。
1.2 通过Runnable接口实现
public class CreateThread implements Runnable(){ public static void mian(String args[]){ Thread t1 = new Runnable(new CreateThread()); t1.start(); } @Override public void run(){ System.out.println("Runnable Thread"); } }
二、 线程终止
Thread提供了stop() 方法,但是这是已经被废弃的方法。因为stop()方法会强行将执行一半的线程终结,可能会引
起数据不一致的情况。
所以停止线程的方法,可以定义一个标记变量,用于指示线程是否需要退出。
三、 线程中断
与线程中断有关的三个方法
public void Thread.interrupt() //中断线程 public boolean Thread.isInterrupted() //判断是否被中断 public static boolean Thread.interrupted //判断是否被中断,并清除当前中断状态
Thread.interrupt()方法是一个实例方法。他通知目标线程中断,也就是设置中断标志。中断标志表示当前线程已经被中断了。
Thread.isInterrupted() 方法也是实例方法,它判断当前线程是否有被中断(通过检查中断标志位)。
Thread.interrupted() 也是用来判断当前线程的中断状态,但同时会清楚线程的中断标志位状态。
Thread.sleep()方法会让当前线程休眠若干时间,它会抛出一个InterruptedException 中断异常。 InterruptedException 不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep() 休眠时,如果被中断,这个异常就会产生。
public static void main(String args[]) throws InterrupterException{ Thread t1 = new Thread(); @Override public void run(){ while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println("Interruted"); break; } try{ Thread.sleep(2000); }catch(InterruptedException e){ System.out.println("Interrupted When Sleep"); //设置中断状态 Thread.currentThread().interrupte(); } Thread.yield(); } }; t1.start(); Thread.sleep(2000); t1.interrupt(); }
执行sleep()方法,会抛出异常,被catch捕获。可以立即退出线程了,但是为了保证数据的完整性,我们可以在
catch中重新设置中断状态(sleep()由于线程中断,会清除中断标记状态),继续后续的处理。
四、wait() 和 nodify()
一个线程调用了object.wait()
就进入了等待队列,直到其他线程调用了object.nodify()
方法。当
object.nodify()
被调用时,会随机从等待队列中选择一个线程,将其唤醒。nodifyAll()
,唤醒等待队列中
的所有线程。
Object.wait() 方法不是可以随便调用的。它必须包含在对应的 synchronized 语句中,无论是wait() 或者 notify() 都需要首先获得目标对象的一个 监视器。而wait() 方法执行后,会释放这个监视器。这样能使其他线程不至于因为该线程的休眠而全部无法正常执行
五、挂起(suspend)和继续执行(resume)线程(已被废弃,不推荐使用)
一个线程执行suspend
方法是会挂起,但是不释放锁,导致其他线程必须等到resume被执行,才能得到锁。如果
resume
在suspend
前不幸被执行,可能导致线程被永久挂起。
六、 等待线程join
两个join方法。
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
第一个join() 方法表示无限等待,它会一直阻塞当前线程,知道目标线程执行完毕((别的线程得等调用了join的线程执行完)。第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
public class JoinMain{ public volatile static int i = 0; public static class AddThread extends Thread{ @Override public void run(){ for(i=0;i<10000000;i++); } } public static void main(String[] args) throws InterruptedException{ AddThread at = new AddThread(); at.start(); at.join(); System.out.println(i); } }
如果不使用join(),可能输出的 i 的值很小,因为AddThread() 没来得及全部执行完,就已经输出了。使用了join(),只有等AddThread() 执行完,才执行其他线程。
join方法本质是让调用线程wait() 在当前线程的实例上。
join方法的核心代码:
while(isAlive){ wait(0); }
七、谦让yield() 方法:
定义如下:
public static native void yield();
调用该方法后,线程会让出CPU,但不意味着不进行CPU的竞争。让出CPU后,还是会进行竞争的。如果认为一
个线程不是那么重要,优先级不是那么高,那么可以适当调用Thread.yield(),让其他线程有更多机会。
八、守护线程Daemon
守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾
回收线程,JIT线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它
会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象
需要守护的了,守护线程自然而然就会退。当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。下面以
一个简单的例子来表述Daemon线程的使用。
public class DaemonDemo { public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { @Override public void run() { while (true) { try { System.out.println("i am alive"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("finally block"); } } } }); daemonThread.setDaemon(true); daemonThread.start(); //确保main线程结束前能给daemonThread能够分到时间片 try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果为:
i am alive finally block i am alive
上面的例子中daemodThread run方法中是一个while死循环,会一直打印,但是当main线程结束后
daemonThread就会退出所以不会出现死循环的情况。main线程先睡眠800ms保证daemonThread能够拥有一次
时间片的机会,也就是说可以正常执行一次打印“i am alive”操作和一次finally块中"finally block"操作。紧接着
main 线程结束后,daemonThread退出,这个时候只打印了"i am alive"并没有打印finnal块中的。因此,这里需
要注意的是守护线程在退出的时候并不会执行finnaly块中的代码,所以将释放资源等操作不要放在finnaly块中执
行,这种操作是不安全的
线程可以通过setDaemon(true)的方法将线程设置为守护线程。并且需要注意的是设置守护线程要先于start()方
法,否则会报
Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.setDaemon(Thread.java:1365) at learn.DaemonDemo.main(DaemonDemo.java:19)
这样的异常,但是该线程还是会执行,只不过会当做正常的用户线程执行。
九、线程状态转化图
参考
《实战Java高并发程序设计》