1.新建线程的三种方式
- 继承自Thread类
- 重写了Thread类中的run方法
public class Thread1 extends Thread{ @Override public void run(){ System.out.println("继承自Thread类"); } public static void main(String[] args) { Thread thread1 = new Thread1(); thread1.start(); } }
- 实现Runnable接口(推荐)
- 利用了Thread中接受Runnable接口的构造函数
public class Thread2 { public static void main(String[] args) { Runnable runnable = new Runnable() { public void run() { System.out.println("实现了Runnable接口"); } }; Thread thread = new Thread(runnable); thread.start(); } }
- 实现Callable接口
- Callable接口和Runnable接口的不同在于Callable接口能够使用FutureTask来接收返回值,且因为FutureTask接口集成自Runnable接口,所以能够使用Thread类的Runnable接口的构造函数
- FutureTask的get方法用来接收返回值,这个方法是阻塞的,只有当线程执行完,才会执行get方法
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Thread3 { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { int count = 0; for(int i=0;i<1000;i++){ count++; } return count; } }; FutureTask futureTask = new FutureTask(call); Thread thread = new Thread(futureTask); thread.start(); System.out.println(futureTask.get()); } }
2.线程的状态
线程的状态如上所示,有以下几种:
- SRART(初始状态):创建了一个线程,处于就绪状态,但是还没有调用start方法
- RUNNABLE(运行状态):调用了start方法,位于可以被运行的线程池中,等待获得CPU的使用权
- RUNNING(运行中):获得CPU的时间片,正在执行程序的代码
- BLOCKED(阻塞状态):处于等待获得锁的状态
5.** WAITING(等待状态)**:已经获得锁,但是因为其他线程占用了资源,因此处于等待 - TIMED_WAITING(限时等待状态):相比于WAITING状态,多了超时退出
- TERMINATED(终止状态):线程执行完毕
3.线程状态的操作
interrupt
与中断相关的是中断标志位,中断时标志位为1,非中断时标志位为0。对于sleep()这种优先级比较低的操作,中断会直接结束睡眠。而对于高优先级操作,不会直接结束,可以通过轮询的方式查看中断标志位,选择是否结束操作。
与中断相关的操作主要有以下三种:
- interrupt():中断操作,设置中断标志位为1
- interrupted():判断调用该线程的中断标志位是否为1,并清除中断标志位
- isInterrupted():判断当前线程的线程的中断标志位是否为1,不会清除中断标志位
下面举个例子来解释下各个操作:
public class Thread5 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
};
Thread thread2 = new Thread() {
public void run() {
while (true) {
}
}
};
thread1.start();
thread2.start();
thread1.interrupt();
thread2.interrupt();
Thread.sleep(1000);
System.out.println(thread1.isInterrupted()); //false
System.out.println(thread2.isInterrupted()); //true
System.out.println(thread1.interrupted()); //false
System.out.println(thread2.interrupted()); //false
}
}
对于第一个输出,因为thread1抛出了InterruptedException,因此会清除中断标志位。
对于第二个输出,因为thread2没有抛出异常,所以中断标志位为1
对于第三个、第四个输出,因为interrupted()函数是判断调用当前线程的线程的中断标志位,而主线程没有被中断,所以为false。
join
join()方法的作用是在多个线程的协作之间,因为不同线程的完成时间不一样,有的块有的慢。但是有时候我们需要一个线程完成之后,后一个线程才能执行,就要用到join()方法。
我们来看一下join方法的源码:
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) {
//这个是核心方法,如果isAlive()一直返回true,那么就会一直执行下去
while (isAlive()) {
wait(0);
}
} else {
//这个是核心方法,如果isAlive()就会进入该代码块
while (isAlive()) {
long delay = millis - now;
//因为这个方法是有限时等待的,如果超过这个时间,就不会继续等待了
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
isAlive方法是其中的核心方法, 用来判断当前线程是否已经结束了
下面是使用join方法的一个例子,创建了10个线程,每个线程都要等前一个线程执行完后,才能执行下一个线程:
public class Thread6 {
public static void main(String[] args) {
Thread curThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Thread7(curThread));
thread.start();
curThread = thread;
}
}
}
class Thread7 implements Runnable {
private Thread curThread;
public Thread7(Thread curThread) {
this.curThread = curThread;
}
public void run() {
try {
curThread.join();
System.out.println(curThread.getName() + " terminated");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
sleep
sleep方法的作用是让线程休眠指定时间,当sleep时被中断,则会抛出InterruptedException。
public static native void sleep(long millis) throws InterruptedException;
有一点需要注意的是,Thread.sleep()和Object.wait()的区别
- sleep是Thread的静态方法,而wait是Object的实例方法。
- sleep在任何地方都可以使用,而wait必须在同步块中使用,并且该对象已经获得锁。sleep方法不会释放锁,只会让出CPU给其他线程执行。wait方法会释放锁,重新进入等待池,等待获取下一次资源
- sleep方法结束休眠之后,当获得CPU之后就会继续执行。而wait方法必须等待其它线程执行Object.notify()或Object.notifyAll()方法之后,才会离开等待池,重新等待CPU。
yield
yield方法是一个静态方法,作用是让当前线程让出自己的时间片,把这些时间片分配给相同优先级的线程。
public static native void yield();
Daemon
守护线程Daemon表示一些在后台运行的线程,例如垃圾GC。当主线程结束了,也就没有必要再守护了。如下面这个例子:
public class Thread8 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
try {
System.out.println("I'am alive");
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("block");
}
}
}
};
thread.setDaemon(true);
thread.start();
Thread.currentThread().sleep(1000);
}
}
输出结果:
从输出结果可以观察到,当主线程结束时,守护线程不会调用finally方法。
此外,要注意的是setDaemon方法必须要再start方法之前设置。